nti.externalization¶
A flexible, schema-driven system for converting Python objects to and from external formats such as JSON and YAML. This works well in a zope.component environment such as Pyramid.
Documentation is hosted at https://ntiexternalization.readthedocs.io/
Introduction and Basics¶
Basic Usage¶
This document provides an overview of nti.externalization
and
shows some simple examples of its usage.
Reading through the Glossary before beginning is highly recommended.
Motivation and Use-cases¶
This package provides a supporting framework for transforming to and from Python objects and an arbitrary binary or textual representation. That representation is self-describing and intended to be stable. Uses for that representation include:
- Communicating with browsers or other clients over HTTP (e.g., AJAX) or sockets;
- Storing persistently on disk for later reconstituting the Python objects;
- Using as a human-readable configuration format (with the proper choice of representation)
We expect that there will be lots of such objects in an application, and we want to make it as easy as possible to communicate them. Ideally, we want to be able to completely automate that, handing the entire task off to this package.
It is also important that when we read external input, we validate that it meets any internal constraints before further processing. For example, numbers should be within their allowed range, or references to other objects should actually result in an object of the expected type.
Finally, we don’t want to have to intermingle any code for reading and writing objects with our actual business (application) logic. The two concerns should be kept as separated as possible from our model objects. Ideally, we should be able to use third-party objects that we have no control over seamlessly in external and internal data.
Getting Started¶
In its simplest form, there are two functions you’ll use to externalize and internalize objects:
>>> from nti.externalization import to_external_object
>>> from nti.externalization import update_from_external_object
We can define an object that we want to externalize:
Caution
The examples in this section are not representative of best practices or preferred patterns. Please keep reading.
class InternalObject(object):
def __init__(self, id=''):
self._field1 = 'a'
self._field2 = 42
self._id = id
def toExternalObject(self, request=None, **kwargs):
return {'A Letter': self._field1, 'The Number': self._field2}
def __repr__(self):
return '<%s %r letter=%r number=%d>' % (
self.__class__.__name__, self._id, self._field1, self._field2)
And we can externalize it with
to_external_object
:
>>> from pprint import pprint
>>> pprint(to_external_object(InternalObject()))
{'A Letter': 'a', 'The Number': 42}
If we want to update it, we need to write the corresponding method:
class UpdateInternalObject(InternalObject):
def updateFromExternalObject(self, external_object, context=None):
self._field1 = external_object['A Letter']
self._field2 = external_object['The Number']
Updating it uses update_from_external_object
:
>>> internal = UpdateInternalObject('internal')
>>> internal
<UpdateInternalObject 'internal' letter='a' number=42>
>>> update_from_external_object(internal, {'A Letter': 'b', 'The Number': 3})
<UpdateInternalObject 'internal' letter='b' number=3>
That’s Not Good Enough¶
Notice that we had to define procedurally the input and output steps in our classes. For some (small) applications, that may be good enough, but it doesn’t come anywhere close to meeting our motivations:
- By mingling the externalization code into our business objects, it makes them larger and muddies their true purpose.
- There’s nothing doing any validation. Any such checking is left up to the object itself.
- It’s manual code to write and test for each of the many objects we can communicate. There’s nothing automatic about it.
Let’s see how this package helps us address each of those concerns in turn.
Adapters and Configuration¶
This package makes heavy use of the Zope Component Architecture to
abstract away details and separate concerns. Most commonly this is
configured using ZCML, and this package
ships with a configure.zcml
that you should load:
>>> from zope.configuration import xmlconfig
>>> import nti.externalization
>>> xmlconfig.file('configure.zcml', nti.externalization)
<zope.configuration.config.ConfigurationMachine ...>
The toExternalObject
method is defined by the
nti.externalization.interfaces.IInternalObjectExternalizer
interface, and the updateFromExternalObject
method is defined by
nti.externalization.interfaces.IInternalObjectUpdater
interface.
Because it is common that one object both reads and writes the
external representation, the two interfaces are joined together in
nti.externalization.interfaces.IInternalObjectIO
. Let’s create a new
internal object:
class InternalObject(object):
def __init__(self, id=''):
self._field1 = 'a'
self._field2 = 42
self._id = id
def __repr__(self):
return '<%s %r letter=%r number=%d>' % (
self.__class__.__name__, self._id, self._field1, self._field2)
Now we will write an IInternalObjectIO
adapter for it:
from zope.interface import implementer
from zope.component import adapter
from nti.externalization.interfaces import IInternalObjectIO
from nti.externalization.datastructures import StandardInternalObjectExternalizer
@implementer(IInternalObjectIO)
@adapter(InternalObject)
class InternalObjectIO(StandardInternalObjectExternalizer):
def __init__(self, context):
super().__init__(context)
# Setting this is optional, if we don't like the default
self.__external_class_name__ = 'ExternalObject'
def toExternalObject(self, **kwargs):
result = super(InternalObjectIO, self).toExternalObject(**kwargs)
result.update({
'Letter': self.context._field1,
'Number': self.context._field2
})
return result
def updateFromExternalObject(self, external_object, context=None):
self.context._field1 = external_object['Letter']
self.context._field2 = external_object['Number']
Tip
It is a best practice for custom externalizers to either extend an
existing datastructure, typically
StandardInternalObjectExternalizer
for simple cases (as in the
example above), or to begin with result =
to_standard_external_dictionary(self.context)
and update that
mapping in place. (The StandardInternalObjectExternalizer
calls
to_standard_external_dictionary
under the covers.)
Caution
The signature for toExternalObject
is poorly defined right now.
The suitable keyword arguments should be enumerated and documented,
but they are not. See https://github.com/NextThought/nti.externalization/issues/54
We can register the adapter (normally this would be done in ZCML) and use it:
<configure xmlns="http://namespaces.zope.org/zope">
<include package="nti.externalization" />
<adapter factory=".InternalObjectIO" />
</configure>
Because we don’t have a Python package to put this ZCML in, we’ll register it manually.
>>> from zope import component
>>> component.provideAdapter(InternalObjectIO, provides=IInternalObjectIO)
>>> internal = InternalObject('original')
>>> internal
<InternalObject 'original' letter='a' number=42>
>>> pprint(to_external_object(internal))
{'Class': 'ExternalObject', 'Letter': 'a', 'Number': 42}
>>> update_from_external_object(internal, {'Letter': 'b', 'Number': 3})
<InternalObject 'original' letter='b' number=3>
Notice that the external form included the Class
key; this is one
of the StandardExternalFields
automatically recognized by the built-in externalizers, whose value is
taken from the corresponding key named in
StandardInternalFields
. There are
others:
>>> internal.creator = u'sjohnson'
>>> internal.createdTime = 123456
>>> pprint(to_external_object(internal))
{'Class': 'ExternalObject',
'CreatedTime': 123456,
'Creator': 'sjohnson',
'Letter': 'b',
'Number': 3}
Note
Notice how the names of the internal fields, creator
and
createdTime
because Creator
and CreatedTime
in
the external object. The convention used by this library is
that fields that cannot be modified directly by the client
are always capitalized. Your custom externalizers and
interface definitions should follow this convention.
By using adapters like this, we can separate out externalization from our core logic. Of course, that’s still a lot of manual code to write.
Using Schemas for Validation and Automatic Externalization¶
Most application objects will implement one or more interfaces. When
those interfaces contain attributes from zope.schema.field
or
nti.schema.field
, they are also called schemas. This package can
automate the entire externalization process, including validation,
based on the schemas an object implements.
Let’s start by writing a simple schema.
from zope.interface import Interface
from zope.interface import taggedValue
from nti.schema.field import ValidTextLine
class IAddress(Interface):
full_name = ValidTextLine(title=u"First name", required=True)
street_address_1 = ValidTextLine(title=u"Street line 1",
max_length=75, required=True)
street_address_2 = ValidTextLine(title=u"Street line 2",
required=False, max_length=75)
city = ValidTextLine(title=u"City name", required=True)
state = ValidTextLine(title=u"State name",
required=False, max_length=10)
postal_code = ValidTextLine(title=u"Postal code",
required=False, max_length=30)
country = ValidTextLine(title=u"Nation name", required=True)
And now an implementation of that interface.
from nti.schema.fieldproperty import createDirectFieldProperties
from nti.schema.schema import SchemaConfigured
@implementer(IAddress)
class Address(SchemaConfigured):
createDirectFieldProperties(IAddress)
Externalizing (and updating!) based on the schema is done with InterfaceObjectIO
.
We’ll create a subclass to configure it.
from nti.externalization.datastructures import InterfaceObjectIO
@adapter(IAddress)
class AddressIO(InterfaceObjectIO):
_ext_iface_upper_bound = IAddress
Now we can register and use it as before:
>>> component.provideAdapter(AddressIO)
>>> address = Address(full_name=u'Steve Jobs',
... street_address_1=u'One Infinite Loop',
... city=u'Cupertino',
... state=u'CA',
... postal_code=u'95014',
... country=u'USA')
>>> external = to_external_object(address)
>>> pprint(external)
{'Class': 'Address',
'city': 'Cupertino',
'country': 'USA',
'full_name': 'Steve Jobs',
'postal_code': '95014',
'state': 'CA',
'street_address_1': 'One Infinite Loop',
'street_address_2': None}
Oops, One Infinte Loop was Apple’s old address. They’ve since moved into their new headquarters:
>>> external['street_address_1'] = u'One Apple Park Way'
>>> _ = update_from_external_object(address, external)
>>> address.street_address_1
'One Apple Park Way'
Notice that our schema declared a number of constraints. For instance,
the full_name
is required, and the state
cannot be longer than
ten characters. Let’s see what happens when we try to violate these
conditions:
>>> external['state'] = u'Commonwealth of Massachusetts'
>>> update_from_external_object(address, external)
Traceback (most recent call last):
...
TooLong: ('State is too long.', 'state', u'Commonwealth of Massachusetts')
>>> external['state'] = u'CA'
>>> external['full_name'] = None
>>> update_from_external_object(address, external)
Traceback (most recent call last):
...
zope.schema._bootstrapinterfaces.RequiredMissing: full_name
Much better! We get validation of our constraints and we didn’t have to write much code. But, we still had to write some code, one class for each object we’re externalizing. Can we do better?
autoPackageIO: Handing responsibility to the framework¶
The answer is yes, we can do much better, with the
ext:registerAutoPackageIO
ZCML directive.
Note
ext:registerAutoPackageIO
is biased for a conventional setup of
a single package: one or more root interfaces in interfaces.py
,
one or more modules defining factories (classes) implementing those
interfaces. To an extent this can be changed using the iobase
argument.
The above example schema is taken from the tests distributed with this
package in nti.externalization.tests.benchmarks
. That package
provides the schema (as shown above), an implementation of it, and
the ZCML file that pulls it all together with one directive.
Here’s the schema, along with several other schema to define a rich
user profile, in interfaces.py
:
# -*- coding: utf-8 -*-
"""
A rich user profile.
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from zope.interface import Interface
from zope.interface import taggedValue
from zope.schema import Object
from zope.schema import URI
from nti.schema.field import Dict
from nti.schema.field import TextLine
from nti.schema.field import ValidTextLine
from nti.schema.field import DecodingValidTextLine
from nti.externalization.tests.benchmarks.bootstrapinterfaces import IRootInterface
from nti.externalization.tests.benchmarks.bootstrapinterfaces import checkEmailAddress
from nti.externalization.tests.benchmarks.bootstrapinterfaces import checkRealname
# pylint:disable=inherit-non-class
class IFriendlyNamed(Interface):
alias = TextLine(title=u'Display alias',
description=u"Enter preferred display name alias, e.g., johnnyboy."
u"Your site may impose limitations on this value.",
required=False)
realname = TextLine(title=u'Full Name aka realname',
description=u"Enter full name, e.g. John Smith.",
required=False,
constraint=checkRealname)
class IAvatarURL(Interface):
"""
Something that features a display URL.
"""
avatarURL = URI(title=u"URL of your avatar picture",
description=u"If not provided, one will be generated for you.",
required=False)
class IBackgroundURL(Interface):
backgroundURL = URI(title=u"URL of your background picture",
description=u"If not provided, one will be generated for you.",
required=False)
class IProfileAvatarURL(IAvatarURL, IBackgroundURL):
pass
class IAddress(IRootInterface):
full_name = ValidTextLine(title=u"First name", required=True)
street_address_1 = ValidTextLine(title=u"Street line 1",
max_length=75, required=True)
street_address_2 = ValidTextLine(title=u"Street line 2",
required=False, max_length=75)
city = ValidTextLine(title=u"City name", required=True)
state = ValidTextLine(title=u"State name",
required=False, max_length=10)
postal_code = ValidTextLine(title=u"Postal code",
required=False, max_length=30)
country = ValidTextLine(title=u"Nation name", required=True)
taggedValue('__external_class_name__',
'Address')
class IUserContactProfile(Interface):
addresses = Dict(title=u"A mapping of address objects.",
key_type=DecodingValidTextLine(title=u"Adresss key"),
value_type=Object(IAddress),
min_length=0,
required=False)
phones = Dict(title=u"A mapping of phone numbers objects.",
key_type=ValidTextLine(title=u"Phone key"),
value_type=ValidTextLine(title=u"A phone"),
min_length=0,
required=False)
contact_emails = Dict(title=u"A mapping of contact emails.",
key_type=DecodingValidTextLine(title=u"Email key"),
value_type=ValidTextLine(title=u'Email',
constraint=checkEmailAddress),
min_length=0,
required=False)
class IUserProfile(IProfileAvatarURL, # pylint:disable=too-many-ancestors
IUserContactProfile,
IFriendlyNamed,
IRootInterface):
"""A user profile"""
taggedValue('__external_class_name__',
'UserProfile')
They are implemented in objects.py
very simply (as above):
from zope import interface
from zope.schema.fieldproperty import createFieldProperties
from nti.schema.eqhash import EqHash
from nti.externalization.representation import WithRepr
from nti.externalization.tests.benchmarks import interfaces
@interface.implementer(interfaces.IAddress)
@EqHash('full_name', 'street_address_1', 'postal_code')
@WithRepr
class Address(SchemaConfigured):
createDirectFieldProperties(interfaces.IAddress)
@interface.implementer(interfaces.IUserProfile)
@EqHash('addresses', 'alias', 'phones', 'realname')
@WithRepr
class UserProfile(SchemaConfigured):
createFieldProperties(interfaces.IUserProfile)
Finally, the ZCML file contains one directive that ties everything together:
<configure xmlns="http://namespaces.zope.org/zope"
xmlns:ext="http://nextthought.com/ntp/ext">
<include package="zope.component" />
<include package="nti.externalization" file="meta.zcml" />
<include package="nti.externalization" />
<ext:registerAutoPackageIO
root_interfaces=".interfaces.IRootInterface"
modules=".objects"
/>
</configure>
If we configure this file, we can create and update addresses. We’ll
do so through their container object, the UserProfile
, thus
demonstrating that nested schemas and objects are possible.
>>> import nti.externalization.tests.benchmarks
>>> _ = xmlconfig.file('configure.zcml', nti.externalization.tests.benchmarks)
>>> from nti.externalization.tests.benchmarks.objects import Address
>>> from nti.externalization.tests.benchmarks.objects import UserProfile
>>> home_address = Address(
... full_name=u'Steve Jobs',
... street_address_1=u'1313 Mockingbird Lane',
... city=u'Salem',
... state=u'MA',
... postal_code=u'6666',
... country=u'USA',
... )
>>> work_address = Address(
... full_name=u'Apple',
... street_address_1=u'1 Infinite Loop',
... city=u'Cupertino',
... state=u'CA',
... postal_code=u'55555',
... country=u'USA',
... )
>>> user_profile = UserProfile(
... addresses={u'home': home_address, u'work': work_address},
... phones={u'home': u'405-555-1212', u'work': u'405-555-2323'},
... contact_emails={u'home': u'steve.jobs@gmail.com', u'work': u'steve@apple.com'},
... avatarURL='http://apple.com/steve.png',
... backgroundURL='https://apple.com/bg.jpeg',
... alias=u'Steve',
... realname=u'Steve Jobs',
... )
>>> external = to_external_object(user_profile)
>>> pprint(external)
{'Class': 'UserProfile',
'MimeType': 'application/vnd.nextthought.benchmarks.userprofile',
'addresses': {'home': {'Class': 'Address',
'MimeType': 'application/vnd.nextthought.benchmarks.address',
'city': 'Salem',
'country': 'USA',
'full_name': 'Steve Jobs',
'postal_code': '6666',
'state': 'MA',
'street_address_1': '1313 Mockingbird Lane',
'street_address_2': None},
'work': {'Class': 'Address',
'MimeType': 'application/vnd.nextthought.benchmarks.address',
'city': 'Cupertino',
'country': 'USA',
'full_name': 'Apple',
'postal_code': '55555',
'state': 'CA',
'street_address_1': '1 Infinite Loop',
'street_address_2': None}},
'alias': 'Steve',
'avatarURL': 'http://apple.com/steve.png',
'backgroundURL': 'https://apple.com/bg.jpeg',
'contact_emails': {'home': 'steve.jobs@gmail.com', 'work': 'steve@apple.com'},
'phones': {'home': '405-555-1212', 'work': '405-555-2323'},
'realname': 'Steve Jobs'}
Notice that there are some additional bits of data in the external
form that are not specified in the interface. Here, that’s Class
and MimeType
. These are two of the Standard Fields.
Let’s make a change to the work address:
>>> external['addresses'][u'work']['street_address_1'] = u'One Apple Park Way'
>>> _ = update_from_external_object(user_profile, external)
>>> user_profile.addresses['work'].street_address_1
'One Apple Park Way'
Importantly, note that, by default, the nested objects are created fresh and not mutated.
>>> user_profile.addresses['work'] is work_address
False
This is described in more detail in Factories.
Representations¶
Being able to get a Python dictionary from an object, and update an object given a Python dictionary, is nice, but it doesn’t go all the way toward solving the goals of this package, interoperating with remote clients using a text (or byte) based stream.
For that, we have the nti.externalization.representation
module, and its key interface
IExternalObjectIO
.
A representation is a format that can serialize Python dictionaries
to text, and given that text, produce a Python dictionary. This
package provides two representations by default, JSON and YAML. These
are named utilities providing IExternalObjectIO
. The function
nti.externalization.to_external_representation
is a shortcut for dumping to a string:
>>> from nti.externalization import to_external_representation
>>> from nti.externalization.interfaces import EXT_REPR_JSON, EXT_REPR_YAML
>>> to_external_representation(address, EXT_REPR_JSON)
'{"Class": "Address", "city": "Cupertino",...
>>> to_external_representation(address, EXT_REPR_YAML)
"{Class: Address, city: Cupertino, country: USA,...
Loading from a string doesn’t have a shortcut, we need to use the utility:
>>> from nti.externalization.interfaces import IExternalObjectIO
>>> external = to_external_object(address)
>>> yaml_io = component.getUtility(IExternalObjectIO, EXT_REPR_YAML)
>>> ext_yaml_str = yaml_io.dump(external)
>>> external_from_yaml = yaml_io.load(ext_yaml_str)
>>> external_from_yaml == external
True
Schemas¶
This package is heavily driven by schemas. The schema determines what attributes of an internal object are externalized based on its fields, and how incoming data in an external object are internalized, including all sorts of validation options.
Here is an example of some schemas.
from zope.interface import Interface
from zope.interface import taggedValue
from zope.schema import List
from zope.schema import Object
from zope.schema import URI
from nti.schema.field import Dict
from nti.schema.field import TextLine
from nti.schema.field import ValidTextLine
from nti.schema.field import DecodingValidTextLine
class IRootInterface(Interface):
pass
class IFriendlyNamed(Interface):
alias = TextLine(title=u'Display alias',
description=u"Enter preferred display name alias, e.g., johnnyboy."
u"Your site may impose limitations on this value.",
required=False)
realname = TextLine(title=u'Full Name aka realname',
description=u"Enter full name, e.g. John Smith.",
required=False,
constraint=checkRealname)
class IAvatarURL(Interface):
"""
Something that features a display URL.
"""
avatarURL = URI(title=u"URL of your avatar picture",
description=u"If not provided, one will be generated for you.",
required=False)
class IBackgroundURL(Interface):
backgroundURL = URI(title=u"URL of your background picture",
description=u"If not provided, one will be generated for you.",
required=False)
class IProfileAvatarURL(IAvatarURL, IBackgroundURL):
pass
class IAddress(IRootInterface):
full_name = ValidTextLine(title=u"First name", required=True)
street_address_1 = ValidTextLine(title=u"Street line 1",
max_length=75, required=True)
street_address_2 = ValidTextLine(title=u"Street line 2",
required=False, max_length=75)
city = ValidTextLine(title=u"City name", required=True)
state = ValidTextLine(title=u"State name",
required=False, max_length=10)
postal_code = ValidTextLine(title=u"Postal code",
required=False, max_length=30)
country = ValidTextLine(title=u"Nation name", required=True)
taggedValue('__external_class_name__',
'Address')
class IUserContactProfile(Interface):
addresses = Dict(title=u"A mapping of address objects.",
key_type=DecodingValidTextLine(title=u"Adresss key"),
value_type=Object(IAddress),
min_length=0,
required=False)
phones = Dict(title=u"A mapping of phone numbers objects.",
key_type=ValidTextLine(title=u"Phone key"),
value_type=ValidTextLine(title=u"A phone"),
min_length=0,
required=False)
contact_emails = Dict(title=u"A mapping of contact emails.",
key_type=DecodingValidTextLine(title=u"Email key"),
value_type=ValidTextLine(title=u'Email',
constraint=checkEmailAddress),
min_length=0,
required=False)
class IUserProfile(IProfileAvatarURL,
IUserContactProfile,
IFriendlyNamed,
IRootInterface):
"""A user profile"""
taggedValue('__external_class_name__',
'UserProfile')
Externalization¶
Standard Fields¶
Certain fields are generally useful for almost all objects. The names
of these fields along with descriptions of their expected contents are
contained in the namespace
StandardExternalFields
.
The function to_standard_external_dictionary
is used to find and
populate many of these fields. It is called automatically by the
datastructures such as InterfaceObjectIO
Decorating¶
Many times, we have additional information we want to include with an
external object that is somehow derived or not explicitly represented
in the interfaces implemented by an object. For example, in a web
application, we may want to provide an href
value giving the URL
at which a particular object may be found. This URL is derived from
the currently executing request object in addition to the object being
externalized.
For this purpose, we use decorators. Decorators are subscription
adapters (or subscribers), meaning that there can be many of them
registered for any given object, that implement
IExternalObjectDecorator
.
They can be registered just for the object, or for the object and the
request. Each time an object is externalized by
to_external_object
, the registered decorators
will be invoked before the external object is returned.
Let’s continue with the example address we used before.
>>> import nti.externalization.tests.benchmarks
>>> from zope.configuration import xmlconfig
>>> _ = xmlconfig.file('configure.zcml', nti.externalization.tests.benchmarks)
>>> from nti.externalization.tests.benchmarks.objects import Address
>>> from nti.externalization.tests.benchmarks.objects import UserProfile
>>> home_address = Address(
... full_name=u'Steve Jobs',
... street_address_1=u'1313 Mockingbird Lane',
... city=u'Salem',
... state=u'MA',
... postal_code=u'6666',
... country=u'USA',
... )
This time we’ll create and register a decorator. Let’s pretend that we
work in a sensitive system and we need to redact the addresses of
users to meet security concerns. Notice that decorators are usually
stateless, so it is faster to make them inherit from Singleton
.
from zope.interface import implementer
from zope.component import adapter
from nti.externalization.interfaces import IExternalObjectDecorator
from nti.externalization.tests.benchmarks.interfaces import IAddress
from nti.externalization.singleton import Singleton
@implementer(IExternalObjectDecorator)
@adapter(IAddress)
class PrivateAddressDecorator(Singleton):
def decorateExternalObject(self, address, external):
for key in 'street_address_1', 'street_address_2', 'state', 'city', 'postal_code':
del external[key]
We’ll register our adapter and externalize:
>>> from nti.externalization import to_external_object
>>> from zope import component
>>> component.provideSubscriptionAdapter(PrivateAddressDecorator)
>>> from pprint import pprint
>>> pprint(to_external_object(home_address))
{'Class': 'Address',
'MimeType': 'application/vnd.nextthought.benchmarks.address',
'country': 'USA',
'full_name': 'Steve Jobs'}
If we provide a request, adapters for the (object, request) are also found:
class Request(object):
url = 'http://example.com/path/'
@implementer(IExternalObjectDecorator)
@adapter(IAddress, Request)
class LinkAddressDecorator(object):
def __init__(self, context, request):
self.request = request
def decorateExternalObject(self, address, external):
external['href'] = self.request.url + 'address'
We can now provide a request when we externalize (if no request
argument is given, the hook function get_current_request
is used to
look for a request):
>>> component.provideSubscriptionAdapter(LinkAddressDecorator)
>>> pprint(to_external_object(home_address, request=Request()))
{'Class': 'Address',
'MimeType': 'application/vnd.nextthought.benchmarks.address',
'country': 'USA',
'full_name': 'Steve Jobs',
'href': 'http://example.com/path/address'}
IExternalStandardDictionaryDecorator¶
There is also
IExternalStandardDictionaryDecorator
. It is
called by to_standard_external_dictionary
. Typically that’s well
before most of the object-specific fields have been filled in (e.g.,
from the object schema), and it is always before
IExternalObjectDecorator
is used. There may be occasional uses for
this, but it’s best to stick to IExternalObjectDecorator
.
Dublin Core Metadata¶
Decorators for zope.dublincore
metadata are installed for all
objects by default. See nti.externalization.dublincore
for more
information.
Internalization¶
We can create or update an existing object using external data with
the functions new_from_external_object()
or
update_from_external_object()
, respectively.
In a web framework like Pyramid where an application object is located
by route matching or traversal, update_from_external_object
makes the most sense.
Factories¶
While updating objects, internalization will, by default, create a new
instance for every mapping that contains a MimeType
or Class
key using Zope Component factories. Factories are named utilities,
that implement IFactory
(essentially, a callable of no arguments)
This package uses extensions of that interface, namely
IMimeObjectFactory
,
IClassObjectFactory
and
IAnonymousObjectFactory
(whose
default implementations are found in
nti.externalization.factory
).
The factory matching MimeType
is preferred, but we can fall back
to one matching Class
if needed.
Factories are usually registered automatically by
ext:registerAutoPackageIO
at the
same time it creates the InterfaceObjectIO
adapters.
You can manually register factories from particular modules using
ext:registerMimeFactories
.
Lets look at the factories registered for our address:
>>> from zope.configuration import xmlconfig
>>> from zope import component
>>> from nti.externalization.interfaces import IMimeObjectFactory
>>> import nti.externalization.tests.benchmarks
>>> _ = xmlconfig.file('configure.zcml', nti.externalization.tests.benchmarks)
>>> factory = component.getUtility(IMimeObjectFactory, 'application/vnd.nextthought.benchmarks.address')
>>> factory
<MimeObjectFactory titled 'Address' using <class 'nti.externalization.tests.benchmarks.objects.Address'>...>
>>> factory()
<nti.externalization.tests.benchmarks.objects.Address ...>
Mime Types are found in the class attribute mimeType
;
ext:registerAutoPackageIO
will add computed mimeType
values to
factory objects if needed.
Special Attributes¶
This document covers some of the special attributes used by this module.
When we discuss interfaces or the contents of attributes, we are
referring to tagged values
. Unless
otherwise noted, tagged values must be set directly on the object in
question; they are not inherited from parent objects.
__external_class_name__
Used an a class or interface to determine the value of the Class
standard external value. Usually this is a string, but when using
InterfaceObjectIO
(including ext:registerAutoPackageIO
) it can
be a callable returning a string or None
This value is inherited; the first non-None value in the resolution order will be used (the distinction matters when using callables).
See also
AutoPackageSearchingScopedInterfaceObjectIO._ap_compute_external_class_name_from_interface_and_instance
and AutoPackageSearchingScopedInterfaceObjectIO._ap_find_factories
__external_can_create__
This boolean value is set to true on factory functions (e.g.,
classes). ext:registerAutoPackageIO
sets it to true automatically.
mimeType
Part of the zope.mimetype.interfaces.IContentTypeAware
interface. This is read from Factories when creating factory
registrations. It also forms one of the standard external fields.
_ext_excluded_out
A tagged value on individual attributes of an interface to prevent
them from being externalized. See InterfaceObjectIO
.
__external_factory_wants_arg__
(Provisional). Attribute of a factory. When creating sub-objects and invoking a factory, should we pass the external object to the factory? If not true or not set, the factory receives no arguments.
New in version 1.0a3.
__external_default_implementation__
(Provisional). Tagged value of an interface implemented by one of the
factories discovered by ext:registerAutoPackageIO
holding the
primary factory discovered for that interface. Used when internalizing
anonymous external data.
This value is inherited.
New in version 1.0a8.
__external_accept_id__
For attributes. Documentation needed.
_ext_is_marker_interface
When searching for a schema to use for externalization, interfaces with this tagged value directly set will not be considered.
Glossary¶
- schema
A
zope.interface.Interface
interface, whose attributes arezope.schema.Field
instances.zope.schema
defines a number of useful field types such aszope.schema.Date
.nti.schema.field
provides even more, such asnti.schema.field.Variant
.See Schemas for more information.
- internal object
- A Python object in the application domain (sometimes known as a “model” object). This may be a complex object consisting of multiple nested objects. It may use inheritance. It will implement one or more schema.
- external object
A dictionary with string keys, and values that are strings, numbers (including booleans), None, lists of external objects, or external objects.
In other words, a simplified interchange format capable of being represented by many programming languages and text formats.
- standard external object
- An external object produced by this package and having some
defined keys and values. The most important of these is
StandardExternalFields.MIMETYPE
, which helps identify the class of the internal object. - anonymous external object
- An external object often not produced by this package and lacking the defining metadata this package produces.
- external representation
The byte string resulting from converting an external object into a particular textual interchange format such as JSON or YAML.
External representations are read and written using
IExternalObjectIO
utilities.- externalization
The process of creating a external object from a internal object. The API for this is
nti.externalization.to_external_object()
, and it is customized withIInternalObjectExternalizer
adapters.Sometimes this also means creating the external representation. If done at the same time, the API for this is
nti.externalization.to_external_representation()
.- internalization
The process of taking an external object and using it to mutate an internal object. The API for this is
nti.externalization.update_from_external_object()
.Sub-objects are freshly created using factories.
- factory
A callable object taking no arguments and returning a particular type of internal object. Typically these are (wrappers around) Python classes; the classes typically need to have an attribute
__external_can_create__
set to a true value.
Changes¶
2.3.1 (unreleased)¶
- Nothing changed yet.
2.3.0 (2021-08-02)¶
- Add a new base class,
StandardInternalObjectExternalizer
. See PR 120 and issue 117. - Rename
IExternalMappingDecorator
toIExternalStandardDictionaryDecorator
to emphasize that it is only used if you (directly or through a super class) callto_standard_external_dictionary
. A compatibility alias remains. See PR 120 and issue 118. - Docs-deprecated aliases in
nti.externalization.interfaces
now also emit deprecation warnings at runtime. - Other documentation improvements. Sphinx can now run all the doctests (on Python 3); many doctests are still run an Python 2.
2.2.0 (2021-04-14)¶
- Add support for Python 3.9.
- Depend on BTrees 4.8 and above. This simplifies externalization checks. See issue 111.
2.1.0 (2020-08-03)¶
Add support for “externalization policies.” These are instances of
ExternalizationPolicy
that can be used to tweak certain low-level details of externalization without replacing externalization objects wholesale. They are intended to have a very low performance impact.The only supported detail that can be changed right now is whether the standard created and last modified fields are externalized as Unix timestamps (the default) or as ISO 8601 strings.
See https://github.com/NextThought/nti.externalization/issues/109
2.0.0 (2020-07-02)¶
- Change
ILocatedExternalMapping
: Previously it extended the legacyzope.interface.common.mapping.IFullMapping
. Now it extends the modernzope.interface.common.collections.IMapping
. Note that this does not require mutability unlike the older interface. (TheLocatedExternalDict
class provided by this package is fully mutable and implementsIMutableMapping
. It also continues to implementIFullMapping
, but use of that interface is discouraged.) - Change
ILocatedExternalSequence
: Previously it extended the legacyzope.interface.common.sequence.ISequence
. Now it extends the modernzope.interface.common.collections.ISequence
. Note that this does not require mutability unlike the older interface. (TheLocatedExternalList
class provided by this package is fully mutable and implementsIMutableSequence
.) - Fix the interface resolution order for
LocatedExternalList
. Previously, with zope.interface 5, it began implementing bothIMutableSequence
(the new interface fromzope.interface.common.collections
) as well as the older interfaceISequence
(fromzope.interface.common.sequence
); the two have inconsistent resolution orders. Now, it only implementsIMutableSequence
and a subset of the legacy interfaces that do not conflict. See issue 105.
1.1.3 (2020-06-25)¶
- Correctly fire
IObjectWillUpdateFromExternalEvent
events before updating an object.
1.1.2 (2020-04-07)¶
- Adapt to a change in zope.container 4.4.0 that exposed unsafe
assumptions that
providedBy(obj)
would return the exact same object with the exact same state on a subsequent call. This was always a bug in the case of concurrency (e.g., if a different thread calleddirectlyProvides
on the same object, or adjusted the__bases__
of some interface in the IRO); the zope.container changes made it possible without concurrency. See https://github.com/zopefoundation/zope.container/issues/38 and https://github.com/NextThought/nti.externalization/issues/104.
1.1.0 (2020-03-27)¶
- Make instances of
fractions.Fraction
externalize as a string such as"1/3"
. When received by a schema field that can parse this format, such aszope.schema.Rational
(or higher on the numeric tower), this means fractions can be round-tripped. - Support externalizing
decimal.Decimal
objects in the YAML representation.
1.0.0 (2020-03-19)¶
- Add compatibility with, and require, zope.interface 5.0.
- Document which tagged values are inherited and which are not.
- Stop inheriting
_ext_is_marker_interface
.
1.0.0a14 (2019-11-13)¶
- Build with Cython 0.29.14 using ‘3str’ as the language level.
- Add support for Python 3.8.
- Update PyYAML to 5.1 and change the default output style slightly.
- Fix tests with Persistent 4.4.3 and above.
- Support zope.interface 4.7, which lets tagged values on interfaces
be inherited, when using
<registerAutoPackageIO>
on a module that had multiple objects implementing a derived interface. See issue 97.
1.0.0a13 (2018-09-20)¶
- Support
IFromBytes
fields introduced by zope.schema 4.8.0. See issue 92. - Make
validate_field_value
(and by extensionInterfaceObjectIO.update_from_external_object
) callfromObject
defined by any fields for non-byte and non-text data. Previously, only if the field raised aWrongContainedTypeError
wouldfromObject
be called.
1.0.0a12 (2018-09-11)¶
- Add support for zope.schema 4.7.0 and nti.schema 1.5.0. Drop support
for older versions, which includes dropping support for
dm.zope.schema.Object
fields.
1.0.0a11 (2018-08-29)¶
- The
@WithRepr
decorator takes into account the updated default repr of Persistent objects with persistent 4.4 and doesn’t hide it. - Subclasses of
ExternalizableInstanceDict
that have non-str (unicode on Python 2, bytes on Python 3) keys in their__dict__
do not throwTypeError
when externalizing. Instead, the non-str values are converted to strs (using ASCII encoding) and the_p_changed
attribute, if any, is set.
1.0.0a10 (2018-08-21)¶
- The
registry
argument to most functions is deprecated and ignored. Instead of making calls toregistry.queryAdapter
, we now invoke the interface directly. For example,IInternalObjectExternalizer(containedObject)
. This lets individual objects have a say if they already provide the interface without going through the legacy code paths (it also calls__conform__
on the object if needed).
1.0.0a9 (2018-08-20)¶
- Allow subclasses of
InterfaceObjectIO
to have non-frozenset values for_ext_primitive_out_ivars_
. This issues a warning and in the future will be a TypeError.
1.0.0a8 (2018-08-16)¶
- Better support for internalizing anonymous value objects discovered
in a
Dict
value. Now, they won’t raise aComponentLookupError
whenrequire_updater
is True, and they will be given aMimeType
based on the schema (if they don’t have one).
1.0.0a7 (2018-07-31)¶
- Avoid a
TypeError
fromvalidate_named_field_value
when external objects have unicode keys. LocatedExternalDict
objects accept more constructor arguments and allow arbitrary attributes.
1.0.0a6 (2018-07-31)¶
InterfaceObjectIO
only returns an anonymous factory forIDict
fields when it wants objects for the value.StandardExternalFields
andStandardInternalFields
are deprecated aliases innti.externalization.externalization
.update_from_external_object
properly handles the case whereINamedExternalizedObjectFactoryFinder
andIInternalObjectUpdater
are registered with different levels of specificity, and the finder also implementsIInternalObjectUpdater
. Before, the finder would, perhaps incorrectly, be used as the updater.
1.0.0a5 (2018-07-30)¶
- Objects inheriting from
InterfaceObjectIO
and registered with the component registry (in ZCML) forIInternalObjectIO
can still be found and used asINamedExternalizedObjectFactoryFinder
, an interface implemented byInterfaceObjectIO
throughIInternalObjectIOFinder
. A warning will be issued to update the registration (which generally means removing theprovides
line in ZCML). ExternalizableInstanceDict
no longer inherits fromAbstractDynamicIO
, it just implements the same interface (with the exception of many of the_ext
methods). This class is deprecated.- Formally document the
notify_modified
member ofnti.externalization.internalization
.notifyModified
is a deprecated alias.
1.0.0a4 (2018-07-30)¶
- Make
InterfaceObjectIO._ext_self
readable from Python, even though that is not documented (and may change again in the future). Document the intended API,_ext_replacement()
. See issue 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.
1.0.0a3 (2018-07-28)¶
- The
@NoPickle
decorator also works withPersistent
subclasses (and may or may not work with multiple-inheritance subclasses ofPersistent
, depending on the MRO, but that’s always been the case for regular objects). APersistent
subclass being decorated with@NoPickle
doesn’t make much sense, so aRuntimeWarning
is issued. A warning is also issued if the class directly implements one of the pickle protocol methods. - Updating objects that use
createFieldProperties
or otherwise haveFieldProperty
objects in their type is at least 10% faster thanks to avoiding double-validation due to a small monkey-patch onFieldProperty
. See issue 67. - Proxies around objects that implement
toExternalObject
are allowed again; the proxied object’stoExternalObject
will be called. - The signature for
updateFromExternalObject()
has been tightened. It should be(self, external_object, context, **kwargs)
, where**kwargs
is optional, as is context.**kwargs
currently contains nothing useful. Uses ofdataserver=None
in the signature will generate a warning. This may be tightened further in the future. See issue 30. __ext_ignore_updateFromExternalObject__
is officially deprecated and generates a warning.update_from_external_object
caches certain information about the types of the updater objects, making it 8-25% faster.update_from_external_object
mutates sequences contained in a dict in-place instead of overwriting with a new list.update_from_external_object
mutates sequences at the top level instead of returning a new list.- Add support for finding factories for incoming data which do not
specify a MIME type or class field based on the key they are
assigned to. This aids in consuming data produced by foreign systems
or using
Dict
schema fields that require modelled values. See issue 51 and PR 68. - Schemas that use
InterfaceObjectIO
(including through the ZCML directiveregisterAutoPackageIO
) can useDict
fields more easily on internalization (externalization has always worked): They automatically internalize their values by treating theDict
as anonymous external data. - Strings can automatically be adapted into
ITimeDelta
objects.
1.0.0a2 (2018-07-05)¶
- The low levels of externalization no longer catch and hide POSKeyError. This indicates a problem with the database. See https://github.com/NextThought/nti.externalization/issues/60
- Remove support for
object_hook
inupdate_from_external_object
. See https://github.com/NextThought/nti.externalization/issues/29. - A number of deprecated aliases for moved functions have been removed.
- On CPython, some of the modules are compiled as extension modules
using Cython for a 10-30% increase in speed. Set the
PURE_PYTHON
environment variable to disable this at runtime. - The unused, undocumented method
stripSyntheticKeysFromExternalDictionary
was removed from instances ofExternalizableDictionaryMixin
. Use the import instead. - Unused keyword arguments for
to_standard_external_dictionary
andto_minimal_standard_external_dictionary
now produce a warning. In the future, extra keyword arguments will be an error. notifyModified
no longer accepts theeventFactory
argument.- The
notify_modified
alias fornotifyModified
has been removed. - Decorating external mappings and external objects handled
decorate_callback
differently. This argument is only used whendecorate
is false. This argument is also confusing and should be considered deprecated. choose_field
no longer has the undocumented conversion behaviour for the CREATOR external field name.
1.0.0a1 (2017-09-29)¶
- First PyPI release.
- Add support for Python 3.
- Drop support for externalizing to plists. See https://github.com/NextThought/nti.externalization/issues/21
- Reach 100% test coverage and ensure we remain there through CI.
- Introduce
nti.externalization.extension_points
to hold hook functions. Move the Pyramid integration there (and deprecate that). Also move the NTIID support there (but the old name works too). See https://github.com/NextThought/nti.externalization/issues/27 - Deprecate
nti.externalization.internalization.register_legacy_search_module
. See https://github.com/NextThought/nti.externalization/issues/35 - Stop
ext:registerAutoPackageIO
from registering the legacy class-name based factories by default. If you need class-name based factories, there are two options. The first is to explicitly registerIClassObjectFactory
objects in ZCML (we could add a scanning directive to make that more convenient for large numbers of classes), and the second is to setregister_legacy_search_module
to a true value in the ZCML directive forext:registerAutoPackageIO
. Note that we expect the behaviour of this attribute to change in the near future. See https://github.com/NextThought/nti.externalization/issues/33 - Make
ext:registerAutoPackageIO
perform legacy class registrations when the configuration context executes, not when the directive runs. This means that conflicts in legacy class names will be detected at configuration time. It also means that legacy class names can be registered locally withz3c.baseregistry
(previously they were always registered in the global site manager). See https://github.com/NextThought/nti.externalization/issues/28 - Drop dependency on
zope.preference
andzope.annotation
. They were not used by this package, although ourconfigure.zcml
did include them. If you usezope.preference
orzope.annotation
, please include them in your own ZCML file. - Drop hard dependency on Acquisition. It is still used if available and is used in test mode.
- Add public implementations of
IMimeObjectFactory
andIClassObjectFactory
innti.externalization.factory
. - Drop dependency on
nti.zodb
and itsPersistentPropertyHolder
. The datastructures innti.externalization.persistence
no longer extend that class; if you have further subclasses that addnti.zodb.peristentproperty.PropertyHoldingPersistent
properties, you’ll need to be sure to mixin this class now. See https://github.com/NextThought/nti.externalization/issues/43 - Add the
<ext:classObjectFactory>
directive for registeringClass
based factories. (Note: MIME factories are preferred.) - Callers of
to_standard_external_dictionary
(which includes AutoPackageScopedInterfaceIO) will now automatically get aMimeType
value if one can be found. Previously only callers ofto_minimal_standard_external_dictionary
would.
API Details¶
API Details¶
Basics¶
nti.externalization.interfaces
: Interfaces and constants¶
Externalization Interfaces
-
class
ExternalizationPolicy
(use_iso8601_for_unix_timestamp=False)[source]¶ Bases:
object
Adjustment knobs for making tweaks across an entire externalization.
These knobs will tweak low-level details of the externalization format, details that are often in a hot code path where overhead should be kept to a minimum.
Instances of this class are used by registering them as named components in the global site manager. Certain low-level functions accept an optional policy argument that must be an instance of this class; higher level functions accept a policy_name argument that is used to find the registered component. If either argument is not given, then
DEFAULT_EXTERNALIZATION_POLICY
is used instead.Instances are immutable.
This class must not be subclassed; as such, there is no interface for it, merely the class itself.
-
use_iso8601_for_unix_timestamp
¶ Should unix timestamp fields be output as their numeric value, or be converted into an ISO 8601 timestamp string? By default, the numeric value is output. This is known to specifically apply to “Created Time” and “Last Modified.”
-
-
class
LocatedExternalDict
(*args, **kwargs)[source]¶ Bases:
dict
A dictionary that implements
ILocatedExternalMapping
. Returned byto_standard_external_dictionary()
.This class is not
IContentTypeAware
, and it indicates so explicitly by declaring amime_type
value of None.
-
interface
IInternalObjectExternalizer
[source]¶ Implemented by, or adapted from, an object that can be externalized.
-
__external_can_create__
¶ This must be set to true, generally at the class level, for objects that can be created by specifying their Class name.
-
__external_class_name__
¶ If present, the value is a string that is used for the ‘Class’ key in the external dictionary. If not present, the local name of the object’s class is used instead.
-
toExternalObject
(**kwargs)¶ Optional, see this
to_external_object()
.
-
-
interface
INonExternalizableReplacement
[source]¶ This interface may be applied to objects that serve as a replacement for a non-externalized object.
-
interface
INonExternalizableReplacementFactory
[source]¶ An factory object called to make a replacement when some object cannot be externalized.
-
__call__
(obj)¶ Returns: An externalized object to replace the given object. Possibly the given object itself if some higher level will handle it. The returned object may have the INonExternalizableReplacement
interface.
-
-
interface
IExternalObjectDecorator
[source]¶ Used as a subscription adapter (of the object or the object and request) to provide additional information to the externalization of an object after it has been externalized by the primary implementation of
IInternalObjectExternalizer
. Allows for a separation of concerns. These are called in no specific order, and so must operate by mutating the external object.These are called after
IExternalStandardDictionaryDecorator
.-
decorateExternalObject
(origial, external)¶ Decorate the externalized object (which is almost certainly a mapping, though this is not guaranteed).
Parameters: - original – The object that is being externalized. Passed to facilitate using non-classes as decorators.
- external – The externalization of that object, produced
by an implementation of
IInternalObjectExternalizer
or default rules.
Returns: Undefined.
-
-
interface
IExternalStandardDictionaryDecorator
[source]¶ Used as a subscription adapter (of the object or the object and request) to provide additional information to the externalization of an object after it has been externalized by the primary implementation of
IInternalObjectExternalizer
(which in turn must have invokedto_standard_external_dictionary()
).Allows for a separation of concerns. These are called in no specific order, and so must operate by mutating the external object.
These are called before
IExternalObjectDecorator
.Changed in version 2.3.0: Previously this was called
IExternalMappingDecorator
; that name remains as a backward compatibility alias.-
decorateExternalMapping
(original, external)¶ Decorate the externalized object mapping.
Parameters: - original – The object that is being externalized. Passed to facilitate using non-classes as decorators.
- external – The externalization of that object, an
ILocatedExternalMapping
, produced by an implementation ofIInternalObjectExternalizer
or default rules.
Returns: Undefined.
-
-
interface
IExternalizedObject
[source]¶ An object that has already been externalized and needs no further transformation.
-
interface
ILocatedExternalMapping
[source]¶ Extends:
nti.externalization.interfaces.IExternalizedObject
,zope.location.interfaces.ILocation
,zope.interface.common.collections.IMapping
The externalization of an object as a dictionary, maintaining its location information.
-
interface
ILocatedExternalSequence
[source]¶ Extends:
nti.externalization.interfaces.IExternalizedObject
,zope.location.interfaces.ILocation
,zope.interface.common.collections.ISequence
The externalization of an object as a sequence, maintaining its location information.
-
class
LocatedExternalList
[source]¶ Bases:
list
A list that implements
ILocatedExternalSequence
. Returned byto_external_object()
.This class is not
IContentTypeAware
, and it indicates so explicitly by declaring amimeType
value of None.
-
interface
IExternalObjectRepresenter
[source]¶ Something that can represent an external object as a sequence of bytes.
These will be registered as named utilities and may each have slightly different representation characteristics.
-
dump
(obj, fp=None)¶ Write the given object. If
fp
is None, then the string representation will be returned, otherwise, fp specifies a writeable object to which the representation will be written.
-
-
interface
IExternalRepresentationReader
[source]¶ Something that can read an external string, as produced by
IExternalObjectRepresenter
and return an equivalent external value.`-
load
(stream)¶ Load from the stream an external value. String values should be read as unicode.
All objects must support the stream being a sequence of bytes, some may support an open file object.
-
-
interface
IExternalObjectIO
[source]¶ Extends:
nti.externalization.interfaces.IExternalObjectRepresenter
,nti.externalization.interfaces.IExternalRepresentationReader
Something that can read and write external values.
-
EXT_REPR_JSON
= u'json'¶ Constant requesting JSON format data
-
EXT_REPR_YAML
= u'yaml'¶ Constant requesting YAML format data
-
interface
IMimeObjectFactory
[source]¶ Extends:
zope.component.interfaces.IFactory
A factory named for the external mime-type of objects it works with.
-
interface
IClassObjectFactory
[source]¶ Extends:
zope.component.interfaces.IFactory
A factory named for the external class name of objects it works with.
-
interface
IAnonymousObjectFactory
[source]¶ Extends:
zope.component.interfaces.IFactory
A factory for external data that doesn’t identify its object type.
This data is not produced by this library but comes from external sources.
When these are registered as factories (utilities) care must be taken to avoid name clashes (since there are no “natural” unique names).
See the ZCML directive
IAnonymousObjectFactoryDirective
for a simple way to do this.New in version 1.0a3.
-
interface
IExternalizedObjectFactoryFinder
[source]¶ An adapter from an externalized object to something that can find factories.
-
find_factory
(externalized_object)¶ Given an externalized object, return a
zope.component.interfaces.IFactory
to create the proper internal types.Returns: An zope.component.interfaces.IFactory
, orNone
.
-
-
interface
IExternalReferenceResolver
[source]¶ Used as a multi-adapter from an internal object and an external reference to something that can resolve the reference.
-
resolve
(reference)¶ Resolve the external reference and return it.
-
-
interface
INamedExternalizedObjectFactoryFinder
[source]¶ An object that can find factories for particular named external objects.
This is registered as an adapter for particular internal objects, so that internal object, and any schema it implements, can be used to choose the factory for nested objects being updated into it.
-
find_factory_for_named_value
(name, value)¶ Find a factory for the external object value when it is the value with the name name.
This function has three key pieces of information to work with.
First, it is an adapter from an internal object, so it knows the ultimate destination object (the context) where the results of the factory will be set.
Second, it knows the incoming name of the external value.
Third, it knows the actual incoming external value.
For example, if the external data looked like
{'key': 'value'}
a call toupdate_from_external_object(internal_object, data)
would conceptually result in a call that looked like this:adapter = INamedExternalizedObjectFactoryFinder(internal_object) factory = adapter.find_factory_for_named_value('key', 'value')
When the value for the external data is a mutable sequence, this function will be called once for each item in the sequence. So external data of
{'key': [1, 2, 3]}
would result in calls('key', 1)
,('key', 2)
and('key', 3)
.This function can return actual factories that produce fresh objects, or it can return the current object assigned to the ultimate attribute in the context to update that exact object in place. This can be beneficial for persistent objects.
-
-
interface
IInternalObjectUpdater
[source]¶ An adapter that can be used to update an internal object from its externalized representation.
-
__external_resolvers__
¶ For objects who need to perform arbitrary resolution from external forms to internal forms, this attribute is a map from key path to a function of three arguments, the dataserver, the parsed object, and the value to resolve. It should return the new value. Note that the function here is at most a class or static method, not an instance method.
-
__external_oids__
¶ For objects whose external form includes object references (OIDs), this attribute is a list of key paths that should be resolved. The values for the key paths may be singleton items or mutable sequences. Resolution may involve placing a None value for a key.
-
updateFromExternalObject
(externalObject, context, **kwargs)¶ Update the object this is adapting from the external object.
Alternately, the signature can be
updateFromExternalObject(externalObject)
or simplyupdateFromExternalObject(externalObject, **kwargs)
. In this last case,context
will be passed as a value in**kwargs
.Returns: If not None
, a value that can be interpreted as a boolean, indicating whether or not the internal object actually underwent updates. IfNone
, the caller should assume that the object was updated (to allow older code that doesn’t return at all.)
-
-
interface
IInternalObjectIO
[source]¶ Extends:
nti.externalization.interfaces.IInternalObjectExternalizer
,nti.externalization.interfaces.IInternalObjectUpdater
A single object responsible for both reading and writing internal objects in external forms. This is convenient for keeping code organized.
-
interface
IInternalObjectIOFinder
[source]¶ Extends:
nti.externalization.interfaces.INamedExternalizedObjectFactoryFinder
,nti.externalization.interfaces.IInternalObjectIO
Like
IInternalObjectIO
, but this object also gets the chance to find factories for objects.The class
InterfaceObjectIO
implements this interface.
-
interface
IObjectWillUpdateFromExternalEvent
[source]¶ Extends:
zope.interface.interfaces.IObjectEvent
An object will be updated from an external value.
-
root
¶ The object initially passed to update_from_external_object. For nested objects, this will be some ancestor of the object this event is for. For updaters that manually update sub-objects, this isn’t guaranteed to be the actual true root object being updated.
-
external_value
¶ The external value. This is not necessarily a pristine object as decoded from, e.g., JSON. It will be mutated as sub-objects get updated and parsed. For example, strings may get replaced with datetimes, and so on. The consequences of modifying this object in an event subscriber are undefined.
-
-
interface
IObjectModifiedFromExternalEvent
[source]¶ Extends:
zope.lifecycleevent.interfaces.IObjectModifiedEvent
An object has been updated from an external value.
-
external_value
¶ The external value
-
kwargs
¶ The keyword arguments
-
-
class
ObjectModifiedFromExternalEvent
(obj, *descriptions, **kwargs)[source]¶ Bases:
zope.lifecycleevent.ObjectModifiedEvent
Default implementation of
IObjectModifiedFromExternalEvent
.
-
class
StandardExternalFields
[source]¶ Bases:
object
Namespace object defining constants whose values are the keys used in external mappings.
These are text (unicode).
Not all external objects will have all possible keys.
Two special values are collections of metadata, not strings:
ALL
andEXTERNAL_KEYS
.-
ID
¶ An id
-
OID
¶ An identifier specific to this exact object instance
-
HREF
¶ A hyperlink to reach this object
-
INTID
¶ An integer uniquely identifying the object in some scope
-
NTIID
¶ A structured identifier similar to a hyperlink
-
CREATOR
¶ The name of the creator of the object
-
CONTAINER_ID
¶ The name of the container holding the object
-
CREATED_TIME
¶ The floating point value giving the Unix epoch time of the object’s creation
-
LAST_MODIFIED
¶ The floating point value giving the Unix epoch time of the last modification of the object
-
CLASS
¶ ‘Class’: The class of the object. If the object provides
__external_class_name__
it will be used to populate this.
-
LINKS
¶ A dictionary mapping “rel” to more hrefs.
-
MIMETYPE
¶ The MIME type of this object
-
ITEMS
¶ A list or dictionary of external objects contained within this object
-
TOTAL
¶ A counter
-
ITEM_COUNT
¶ The total number of items contained in this object
-
ALL
¶ A collection of all names of all the attributes of this class.
That is, the contents of this collection are the attribute names that give standard external fields. You can iterate this and use
getattr()
to get the corresponding values.
-
EXTERNAL_KEYS
¶ A collection of all values of all attributes of this class.
That is, the contents of this collection are the keys that a standard external object would be expected to have.
-
-
class
StandardInternalFields
[source]¶ Bases:
object
Namespace object defining constants whose values are the property/attribute names looked for on internal objects.
These must be native strings.
-
CONTAINER_ID
¶ ‘containerId’: The ID of the container of this object. Fills
StandardExternalFields.CONTAINER_ID
.
-
CREATED_TIME
¶ ‘createdTime’: The Unix timestamp of the creation of this object. If no value can be found, we will attempt to adapt to
zope.dublincore.interfaces.IDCTimes
and use its ‘created’ attribute. FillsStandardExternalFields.CREATED_TIME
-
CREATOR
¶ ‘creator’: An object that created this object. This will be converted to a text string to fill in
StandardExternalFields.CREATOR
.
-
ID
¶ ‘id’: An object ID
-
LAST_MODIFIED
¶ ‘lastModified’: The Unix timestamp of the last modification of this object. If no value can be found, we will attempt to adapt to zope.dublincore.interfaces.IDCTimes` and use its ‘modified’ attribute. Fills
StandardExternalFields.LAST_MODIFIED
-
NTIID
¶ ‘ntiid’: An object’s structured ID.
-
-
DEFAULT_EXTERNALIZATION_POLICY
= ExternalizationPolicy(use_iso8601_for_unix_timestamp=False)¶ The default externalization policy.
Deprecated Names¶
-
IList
= <InterfaceClass zope.interface.common.builtins.IList>[source]¶ Marker interface for lists.
Deprecated since version 2.1.0: Use
zope.interface.common.builtins.IList
directly. This is just an alias.
-
IIterable
= <ABCInterfaceClass zope.interface.common.collections.IIterable>[source]¶ Base interface for iterable types.
Deprecated since version 2.1.0: Use
zope.interface.common.collections.IIterable
directly. This is just an alias.
-
IExternalObject
= <InterfaceClass nti.externalization.interfaces.IInternalObjectExternalizer>¶ Backwards compatibility alias.
Deprecated since version 2.0.0: Use
IInternalObjectExternalizer
directly.
-
INonExternalizableReplacer
= <InterfaceClass nti.externalization.interfaces.INonExternalizableReplacementFactory>¶ Backwards compatibility alias.
Deprecated since version 2.0.0: Use
INonExternalizableReplacement
directly.
-
IExternalMappingDecorator
= <InterfaceClass nti.externalization.interfaces.IExternalStandardDictionaryDecorator>¶ Backwards compatibility alias.
Deprecated since version 2.3.0: Use
IExternalStandardDictionaryDecorator
directly.
Externalization¶
Functions related to actually externalizing objects.
Only import from this module. Sub-modules of this package are implementation details.
-
get_last_modified_time
(context, default=None, policy=ExternalizationPolicy(use_iso8601_for_unix_timestamp=False), _write_into=None)[source]¶ Find and return a number representing the time since the epoch in fractional seconds at which the
context
was last modified. This is the same value that is used byto_standard_external_dictionary()
, and takes into account whether something isnti.dataserver.interfaces.ILastModified
orzope.dublincore.interfaces.IDCTimes
.Returns: A number if it can be found, or the value of default
-
get_created_time
(context, default=None, policy=ExternalizationPolicy(use_iso8601_for_unix_timestamp=False), _write_into=None)[source]¶ Find and return a number representing the time since the epoch in fractional seconds at which the
context
was created. This is the same value that is used byto_standard_external_dictionary()
, and takes into account whether something isnti.dataserver.interfaces.ILastModified
orzope.dublincore.interfaces.IDCTimes
.Returns: A number if it can be found, or the value of default
-
to_standard_external_dictionary
(self, mergeFrom=None, decorate=True, request=NotGiven)[source]¶ Returns a dictionary representing the standard externalization of the object self. This function takes care of many of the standard external fields:
- External identifiers like
StandardExternalFields.OID
andStandardExternalFields.NTIID
usingset_external_identifiers
. - The
StandardExternalFields.CREATOR
. - The
StandardExternalFields.LAST_MODIFIED
. - The
StandardExternalFields.CREATED_TIME
. - The
StandardExternalFields.CONTAINER_ID
. - The
StandardExternalFields.CLASS
andStandardExternalFields.MIMETYPE
(from themimeType
attribute of the object).
If the object has any
IExternalStandardDictionaryDecorator
subscribers registered for it, they will be called to decorate the result of this method before it returns (unless decorate is set toFalse
; only do this if you know what you are doing! ) This is the only part ofnti.externalization
that invokes this decorator.Custom externalization should begin by calling this function, or, preferably, by using an existing externalizer (which invokes this function, such as
StandardInternalObjectExternalizer
orInterfaceObjectIO
) or subclassing such an existing type and mutating the dictionary returned from super’stoExternalObject
in your own implementation.Parameters: - mergeFrom (dict) – For convenience, if mergeFrom is not
None
, then values it contains will be added to the dictionary created by this method. The keys and values in mergeFrom should already be external. - policy (ExternalizationPolicy) – The
ExternalizationPolicy
to use. Must not be None.
Returns: A
LocatedExternalDict
. For further externalization, this object should be mutated in place.Changed in version 1.0a1: Arbitrary keyword arguments not used by this function are deprecated and produce a warning.
Changed in version 2.1: Add the policy keyword.
- External identifiers like
-
to_minimal_standard_external_dictionary
(self, mergeFrom=None)[source]¶ Does no decoration. Useful for non-‘object’ types. self should have a mime_type field.
-
to_external_object
(obj, name=<default value>, registry=<default value>, catch_components=(), catch_component_action=None, request=<default value>, decorate=True, useCache=True, decorate_callback=<default value>, default_non_externalizable_replacer=<function DefaultNonExternalizableReplacer>, policy_name=<default value>, policy=<default value>)[source]¶ Translates the object into a form suitable for external distribution, through some data formatting process. See
SEQUENCE_TYPES
andMAPPING_TYPES
for details on what we can handle by default.Parameters: - name (string) – The name of the adapter to
IInternalObjectExternalizer
to look for. Defaults to the empty string (the default adapter). If you provide a name, and an adapter is not found, we will still look for the default name (unless the name you supply is None). - catch_components (tuple) – A tuple of exception classes to catch when externalizing sub-objects (e.g., items in a list or dictionary). If one of these exceptions is caught, then catch_component_action will be called to raise or replace the value. The default is to catch nothing.
- catch_component_action (callable) – If given with
catch_components, a function of two arguments, the object
being externalized and the exception raised. May return a
different object (already externalized) or re-raise the
exception. There is no default, but
catch_replace_action()
is a good choice. - default_non_externalizable_replacer (callable) – If we are
asked to externalize an object and cannot, and there is no
INonExternalizableReplacer
registered for it, then call this object and use the results. - request – If given, the request that the object is being externalized on behalf of. If given, then the object decorators will also look for subscribers to the object plus the request (like traversal adapters); this is a good way to separate out request or user specific code.
- decorate_callback – Callable to be invoked in case there is no decaration
- policy – The
ExternalizationPolicy
to use. Takes priority over policy_name. If this is not given (and neither is policy_name), the thread local state is consulted for the current policy established by the most recent caller to this method; if there is no such caller, then theDEFAULT_EXTERNALIZATION_POLICY
is used. - policy_name (str) – If no policy is given, then this is used to lookup a utility. If this is used, the utility must exist.
- name (string) – The name of the adapter to
Internalization¶
Functions for taking externalized objects and creating application model objects.
-
update_from_external_object
(containedObject, externalObject, context=None, require_updater=False, notify=True)[source]¶ Central method for updating objects from external values.
Parameters: - containedObject – The object to update.
- externalObject – The object (typically a mapping or sequence) to update the object from. Usually this is obtained by parsing an external format like JSON.
- context – An object passed to the update methods.
- require_updater – If True (not the default) an exception
will be raised if no implementation of
IInternalObjectUpdater
can be found for the containedObject. - notify (bool) – If
True
(the default), then if the updater for the containedObject either has no preference (returns None) or indicates that the object has changed, then anIObjectModifiedFromExternalEvent
will be fired. This may be a recursive process so a top-level call to this object may spawn multiple events. The events that are fired will have adescriptions
list containing one or moreIAttributes
each withattributes
for each attribute we modify (assuming that the keys in theexternalObject
map one-to-one to an attribute; if this is the case and we can also find an interface declaring the attribute, then theIAttributes
will have the right value forinterface
as well). - pre_hook (callable) – If given, called with the before
update_from_external_object is called for every nested object.
Signature
f(k,x)
wherek
is either the key name, or None in the case of a sequence andx
is the external object. Deprecated.
Returns: containedObject after updates from externalObject
Notifies
IObjectModifiedFromExternalEvent
for each object that is modified, andIObjectWillUpdateFromExternalEvent
before doing so.See also
INamedExternalizedObjectFactoryFinder
Changed in version 1.0.0a2: Remove the
object_hook
parameter.Changed in version 1.1.3: Correctly file
IObjectWillUpdateFromExternalEvent
before updating each object.
-
new_from_external_object
(external_object, *args, **kwargs)[source]¶ Like
update_from_external_object
, but creates a new object to update usingfind_factory_for
.All remaining arguments are passed to
update_from_external_object
.If no factory can be found, raises a
zope.interface.interfaces.ComponentLookupError
.Returns the new object.
New in version 1.0a3.
-
register_legacy_search_module
(module_name)[source]¶ The legacy creation search routines will use the modules registered by this method.
Note that there are no order guarantees about how the modules will be searched. Duplicate class names are thus undefined.
Parameters: module_name – Either the name of a module to look for at runtime in sys.modules
, or a module-like object having a__dict__
.Deprecated since version 1.0: Use explicit mime or class factories instead. See https://github.com/NextThought/nti.externalization/issues/35
-
find_factory_for
(externalized_object) → factory[source]¶ Given a
IExternalizedObject
, locate and return a factory to produce a Python object to hold its contents.If there is a
IExternalizedObjectFactoryFinder
adapter registered for the externalized object, we return the results of itsfind_factory
method. Note that since externalized objects are typically simple lists or dicts, such adapters have the capability to hijack all factory finding, probably unintentionally.Otherwise, we examine the contents of the object itself to find a registered factory based on MIME type (preferably) or class name.
Changed in version 1.0a10: The
registry
argument is deprecated and ignored.
-
notify_modified
(containedObject, externalObject, updater=None, external_keys=None, **kwargs)¶ Create and send an
ObjectModifiedFromExternalEvent
for containedObject usingzope.event.notify
.The containedObject is the subject of the event. The externalObject is the dictionary of data that was used to update the containedObject.
external_keys is list of keys from externalObject that actually changed containedObject. If this is not given, we assume that all keys in externalObject were changed. Note that these should to correspond to the interface fields of interfaces that the containedObject implements in order to properly be able to create and populate the
zope.lifecycleevent
IAttributes
.updater, if given, is the
IInternalObjectUpdater
instance that was used to handle the updates. If this object implements an_ext_adjust_modified_event
method, it will be called to adjust (and return) the event object that will be notified.kwargs are the keyword arguments passed to the event constructor.
Returns: The event object that was notified.
-
validate_field_value
(self, field_name, field, value)[source]¶ Given a
zope.schema.interfaces.IField
object from a schema implemented byself
, validates that the proposed value can be set. If the value needs to be adapted to the schema type for validation to work, this method will attempt that.Parameters: - field_name (str) – The name of the field we are setting. This implementation currently only uses this for informative purposes.
- field (
zope.schema.interfaces.IField
) – The schema field to use to validate (and set) the value.
Raises: zope.interface.Invalid – If the field cannot be validated, along with a good reason (typically better than simply provided by the field itself)
Returns: A callable of no arguments to call to actually set the value (necessary in case the value had to be adapted).
-
validate_named_field_value
(self, iface, field_name, value)[source]¶ Given a
zope.interface.Interface
and the name of one of its attributes, validate that the givenvalue
is appropriate to set. Seevalidate_field_value()
for details.Parameters: field_name (str) – The name of a field contained in iface
. May name a regularzope.interface.Attribute
, or azope.schema.interfaces.IField
; if the latter, extra validation will be possible.Returns: A callable of no arguments to call to actually set the value.
Datastructures¶
Datastructures to help externalization.
-
class
ExternalizableDictionaryMixin
[source]¶ Bases:
object
Implements a toExternalDictionary method as a base for subclasses.
-
_ext_replacement
()[source]¶ Return the object that we are externalizing.
This class returns
self
, but subclasses will typically override this.
-
toExternalDictionary
(mergeFrom=None, *unused_args, **kwargs)[source]¶ Produce the standard external dictionary for this object.
Uses
_ext_replacement
.
-
-
class
StandardInternalObjectExternalizer
(context)[source]¶ Bases:
nti.externalization.datastructures.ExternalizableDictionaryMixin
An adapter that can be used to implement
IInternalObjectExternalizer
.The result of externalizing is the standard external dictionary for this adapter’s context argument.
This can be registered as-is, or subclassed to add additional items in the external dictionary. In that case, always begin by calling this implemention first and updating the result.
New in version 2.3.0.
The constructor sets
__external_can_create__
toFalse
(because creating from just an externalizer makes no sense) and__external_class_name__
toNone
(if you override this value, it will replace theClass
value in the returned dictionary; it must be a nativestr
).
-
class
AbstractDynamicObjectIO
[source]¶ Bases:
nti.externalization.datastructures.ExternalizableDictionaryMixin
Base class for objects that externalize based on dynamic information.
Abstractions are in place to allow subclasses to map external and internal names independently (this type never uses getattr/setattr/hasattr, except for some standard fields).
See
InterfaceObjectIO
for a complete implementation.-
_ext_accept_external_id
(ext_self, parsed)[source]¶ If the object we’re updating does not have an
id
set, but there is anID
in the external object, should we be able to use it?Returns: boolean
-
_ext_accept_update_key
(k, ext_self, ext_keys)[source]¶ Returns whether or not this key should be accepted for setting on the object, or silently ignored.
Parameters: ext_keys – As an optimization, the value of _ext_all_possible_keys()
is passed. Keys are only accepted if they are in this list.
-
_ext_getattr
(object, name[, default]) → value[source]¶ Return the attribute of the ext_self object with the internal name name. If the attribute does not exist, should raise (typically
AttributeError
), unless default is given, in which case it returns that.Changed in version 1.0a4: Add the default argument.
-
_ext_keys
()[source]¶ Return only the names of attributes that should be externalized. These values will be used as keys in the external dictionary.
See
_ext_all_possible_keys()
. This implementation then filters out private attributes (those beginning with an underscore), and those listed in_excluded_in_ivars_
.This method must return a set of native strings.
-
_ext_primitive_keys
()[source]¶ Return a container of string keys whose values are known to be primitive. This is an optimization for writing.
This method must return a frozenset.
-
_ext_replacement
()[source]¶ Return the object that we are externalizing.
This class returns
self
, but subclasses will typically override this.
-
_ext_replacement_getattr
(name, default=<default value>)[source]¶ Like
_ext_getattr
, but automatically fills in_ext_replacement
for the ext_self argument.New in version 1.0a4.
-
find_factory_for_named_value
(key, value)[source]¶ Uses
find_factory_for
to locate a factory.This does not take into account the current object (context) or the key. It only handles finding factories based on the class or MIME type found within value.
-
toExternalDictionary
(mergeFrom=None, *unused_args, **kwargs)[source]¶ Produce the standard external dictionary for this object.
Uses
_ext_replacement
.
-
-
class
ExternalizableInstanceDict
[source]¶ Bases:
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.
Consider carefully before using this class. Generally, an interface and
InterfaceObjectIO
are better.Changed in version 1.0a5: No longer extends
AbstractDynamicObjectIO
, just delegates to it. Most of the_ext_`
prefixed methods can no longer be overridden.-
toExternalObject
(mergeFrom=None, *args, **kwargs)[source]¶ See
toExternalObject
. CallstoExternalDictionary
.
-
-
class
InterfaceObjectIO
(context, iface_upper_bound=None, validate_after_update=True)[source]¶ Bases:
nti.externalization.datastructures.AbstractDynamicObjectIO
Externalizes the context to a dictionary based on getting the attributes of an object defined by an interface. If any attribute has a true value for the tagged value
_ext_excluded_out
, it will not be considered for reading or writing.This is an implementation of
IInternalObjectIOFinder
, meaning it can both internalize (update existing objects) and externalize (producing dictionaries), and that it gets to choose the factories used for sub-objects when internalizing.This class is meant to be used as an adapter, so it accepts the object to externalize in the constructor, as well as the interface to use to guide the process. The object is externalized using the most-derived version of the interface given to the constructor that it implements.
If the interface (or an ancestor) has a tagged value
__external_class_name__
, it can either be the value to use for theClass
key, or a callable__external_class_name__(interface, object ) -> name.
(TODO: In the future extend this to multiple, non-overlapping interfaces, and better interface detection (see
ModuleScopedInterfaceObjectIO
for a limited version of this.)This class overrides
_ext_replacement
to return the context.Parameters: - iface_upper_bound – The upper bound on the schema to use
to externalize
ext_self
; we will use the most derived sub-interface of this interface that the object implements. Subclasses can either override this constructor to pass this parameter (while taking one argument themselves, to be usable as an adapter), or they can define the class attribute_ext_iface_upper_bound
- validate_after_update (bool) – If
True
(the default) then the entire schema will be validated after an object has been updated withupdate_from_external_object()
, not just the keys that were assigned.
-
_ext_accept_external_id
(ext_self, parsed)[source]¶ If the interface we’re working from has a tagged value of
__external_accept_id__
on theid
field, then this will return that value; otherwise, returns false.
-
_ext_getattr
(object, name[, default]) → value[source]¶ Return the attribute of the ext_self object with the internal name name. If the attribute does not exist, should raise (typically
AttributeError
), unless default is given, in which case it returns that.Changed in version 1.0a4: Add the default argument.
-
_ext_replacement
()[source]¶ Return the object that we are externalizing.
This class returns
self
, but subclasses will typically override this.
-
find_factory_for_named_value
(key, value)[source]¶ If
AbstractDynamicObjectIO.find_factory_for_named_value
cannot find a factory based on examining value, then we use the context objects’s schema to find a factory.If the schema contains an attribute named key, it will be queried for the tagged value
__external_factory__
. If present, this tagged value should be the name of a factory object implementingIAnonymousObjectFactory
registered in registry (typically registered in the global site).The ZCML directive
IAnonymousObjectFactoryDirective
sets up both the registration and the tagged value.This is useful for internalizing data from external sources that does not provide a class or MIME field within the data.
The most obvious limitation of this is that if the value is part of a sequence, it must be a homogeneous sequence. The factory is called with no arguments, so the only way to deal with heterogeneous sequences is to subclass this object and override this method to examine the value itself.
A second limitation is that the external data key must match the internal schema field name. Again, the only way to remove this limitation is to subclass this object.
If no registered factory is found, and the schema field is a
zope.schema.Dict
with a value type ofzope.schema.Object
, then we return a factory which will update the object in place.Changed in version 1.0a6: Only return an anonymous factory for
IDict
fields when it wants objects for the value.
-
schema
¶ The schema we will use to guide the process
- iface_upper_bound – The upper bound on the schema to use
to externalize
-
class
ModuleScopedInterfaceObjectIO
(context, iface_upper_bound=None, validate_after_update=True)[source]¶ Bases:
nti.externalization.datastructures.InterfaceObjectIO
Only considers the interfaces provided within a given module (usually declared as a class attribute) when searching for the schema to use to externalize an object; the most derived version of interfaces within that module will be used. Subclasses must declare the class attribute
_ext_search_module
to be a module (something with the__name__
) attribute to locate interfaces in.Suitable for use when all the externalizable fields of interest are declared by an interface within a module, and an object does not implement two unrelated interfaces from the same module.
Note
If the object does implement unrelated interfaces, but one (set) of them is a marker interface (featuring no schema fields or attributes), then it can be tagged with
_ext_is_marker_interface
and it will be excluded when determining the most derived interfaces. This can correct some cases that would otherwise raise a TypeError. This tag is not inherited.Parameters: - iface_upper_bound – The upper bound on the schema to use
to externalize
ext_self
; we will use the most derived sub-interface of this interface that the object implements. Subclasses can either override this constructor to pass this parameter (while taking one argument themselves, to be usable as an adapter), or they can define the class attribute_ext_iface_upper_bound
- validate_after_update (bool) – If
True
(the default) then the entire schema will be validated after an object has been updated withupdate_from_external_object()
, not just the keys that were assigned.
- iface_upper_bound – The upper bound on the schema to use
to externalize
ZCML¶
Directives to be used in ZCML.
These directives are all in the http://nextthought.com/ntp/ext
namespace,
and are loaded using the meta.zcml
file.
Example (non-sensical) of all the directives:
<configure xmlns:ext="http://nextthought.com/ntp/ext">
<include package="nti.externalization" file="meta.zcml" />
<ext:registerMimeFactories module="the.module" />
<ext:registerAutoPackageIO
root_interfaces="other_module.interfaces.IExtRoot"
modules="other_module.factories"
iobase="other_module.externalization.IOBase"
register_legacy_search_module="yes" />
<ext:classObjectFactory
factory="third_module.Factory"
name="AName" />
<ext:anonymousObjectFactory
factory="module3.Factory"
for="interfaces.ISchema"
field="field" />
<ext:anonymousObjectFactoryInPlace
for="interfaces.ISchema"
field="field2" />
</configure>
-
interface
IRegisterInternalizationMimeFactoriesDirective
[source]¶ Defines the
ext:registerMimeFactories
directive.Poke through the classes defined in module. If a class defines the
mimeType
attribute and can be created externally, (because it defines__external_can_create__
to be true), registers a factory utility under themimeType
name. (For backwards compatibility,mime_type
is accepted if there is nomimeType
.)Factories are discovered using
find_factories_in_module
.See
nti.externalization.internalization.find_factory_for()
for how factories are used.-
module
¶ Module to scan for Mime factories to add
Implementation: zope.configuration.fields.GlobalObject
Read Only: False Required: True Default Value: None
-
-
interface
IAutoPackageExternalizationDirective
[source]¶ Defines the
ext:registerAutoPackageIO
directive.This directive combines the effects of
IRegisterInternalizationMimeFactoriesDirective
with that ofautopackage
, removing all need to repeat root interfaces and module names.After this directive is complete, a new class that descends from
AutoPackageSearchingScopedInterfaceObjectIO
will be registered as theIInternalObjectIO
adapter for all of the root_interface objects, and the modules (or factory_modules) will be searched for object factories viaIRegisterInternalizationMimeFactoriesDirective
.Changed in version 1.0: Add the register_legacy_search_module keyword argument, defaulting to False. Previously legacy search modules would always be registered, but now you must explicitly ask for it.
-
root_interfaces
¶ The root interfaces defined by the package.
Implementation: zope.configuration.fields.Tokens
Read Only: False Required: True Default Value: None Allowed Type: list
Value Type
Implementation: zope.configuration.fields.GlobalInterface
Read Only: False Required: True Default Value: None Value Type
Implementation: zope.schema.InterfaceField
Read Only: False Required: True Default Value: None
-
factory_modules
¶ If given, module names that should be searched for internalization factories.
If not given, all modules will be examined. If given, only these modules will be searched.
Implementation: zope.configuration.fields.Tokens
Read Only: False Required: False Default Value: None Allowed Type: list
Value Type
Implementation: zope.configuration.fields.GlobalObject
Read Only: False Required: True Default Value: None
-
register_legacy_search_module
¶ Register found factories by their class name.
If true (not the default), then, in addition to registering factories by their mime type, also register them all by their class name. This is not recommended; currently no conflicts are caught and the order is ill-defined. See https://github.com/NextThought/nti.externalization/issues/33
Implementation: zope.configuration.fields.Bool
Read Only: False Required: False Default Value: False Allowed Type: bool
-
iobase
¶ If given, a base class that will be used. You can customize aspects of externalization that way.
This class should descend from
object
, and it should implement the extension methods documented to customizeAutoPackageSearchingScopedInterfaceObjectIO
.Implementation: zope.configuration.fields.GlobalObject
Read Only: False Required: False Default Value: None
-
modules
¶ Module names that contain the implementations of the root_interfaces.
Implementation: zope.configuration.fields.Tokens
Read Only: False Required: True Default Value: None Allowed Type: list
Value Type
Implementation: zope.configuration.fields.GlobalObject
Read Only: False Required: True Default Value: None
-
-
interface
IClassObjectFactoryDirective
[source]¶ Defines the
ext:classObjectFactory
directive.This directive registers a single
nti.externalization.interfaces.IClassObjectFactory
.The factory will be registered for a class object.
-
factory
¶ The class object that will be created.
This must define the
__external_can_create__
attribute to be true.Implementation: zope.configuration.fields.GlobalObject
Read Only: False Required: True Default Value: None
-
description
¶ Description
Provides a description for the object.
Implementation: zope.configuration.fields.MessageID
Read Only: False Required: False Default Value: None Allowed Type: unicode
-
name
¶ The name for the factory.
If not given, the
__external_class_name__
of the class will be used. If that’s not available, the__name__
will be used.Implementation: zope.configuration.fields.PythonIdentifier
Read Only: False Required: False Default Value: None Allowed Type: str
-
trusted
¶ Ignore any value for
__external_can_create__
on the factory.Implementation: zope.configuration.fields.Bool
Read Only: False Required: False Default Value: False Allowed Type: bool
-
title
¶ Title
Provides a title for the object.
Implementation: zope.configuration.fields.MessageID
Read Only: False Required: False Default Value: None Allowed Type: unicode
-
-
interface
IBaseAnonymousObjectFactoryDirective
[source]¶ The common parts of anonymous object factory directives.
New in version 1.0a3.
-
field
¶ The name of the schema field
The factory results will be assigned to this field.
Implementation: zope.configuration.fields.PythonIdentifier
Read Only: False Required: True Default Value: None Allowed Type: str
-
for_
¶ The interface that is the parent object this will be used for
Implementation: zope.configuration.fields.GlobalInterface
Read Only: False Required: True Default Value: None Value Type
Implementation: zope.schema.InterfaceField
Read Only: False Required: True Default Value: None
-
-
interface
IAnonymousObjectFactoryDirective
[source]¶ Extends:
nti.externalization.zcml.IBaseAnonymousObjectFactoryDirective
Defines the
ext:anonymousObjectFactory
directive.This directive registers a single
nti.externaliaztion.interfaces.IAnonymousObjectFactory
for a single field used within a single object.New in version 1.0a3.
-
trusted
¶ Ignore any value for
__external_can_create__
on the factory.Implementation: zope.configuration.fields.Bool
Read Only: False Required: False Default Value: False Allowed Type: bool
-
factory
¶ The class object that will be created.
Implementation: zope.configuration.fields.GlobalObject
Read Only: False Required: True Default Value: None
-
description
¶ Description
Provides a description for the object.
Implementation: zope.configuration.fields.MessageID
Read Only: False Required: False Default Value: None Allowed Type: unicode
-
pass_external_object_to_factory
¶ Pass the external object to the factory.
If true (not the default), then, the factory will recieve one argument, the anonymous external data. Otherwise, it gets no arguments.
Implementation: zope.configuration.fields.Bool
Read Only: False Required: False Default Value: False Allowed Type: bool
-
title
¶ Title
Provides a title for the object.
Implementation: zope.configuration.fields.MessageID
Read Only: False Required: False Default Value: None Allowed Type: unicode
-
-
interface
IAnonymousObjectFactoryInPlaceDirective
[source]¶ Extends:
nti.externalization.zcml.IBaseAnonymousObjectFactoryDirective
Defines the
anonymousObjectFactoryInPlace
directive.This directive causes the external object itself to be used and updated in place. This is helpful when the object itself is not modelled, but its values are.
Such data might look like this:
{'home': {'MimeType': 'Address', ...}, 'work': {'MimeType': "Address', ...}}
And it might have a schema field that looks like this:
class IAddress(Interface): pass class IHasAddresses(Interface): addresses = Dict( title=u"A mapping of address objects.", key_type=TextLine(title=u"Adresss key"), value_type=Object(IAddress))
The ZCML would then look like this:
<ext:anonymoustObjectFactoryInPlace for="IHasAddresses" field="addresses" />
New in version 1.0a3.
-
find_factories_in_module
(module, case_sensitive=False)[source]¶ Look through the
vars
of module to find any eligible factory functions.An eligible factory is a callable object with a True value for the attribute
__external_can_create__
.If module is really a module, then only objects that are defined in that module will be found. Otherwise (module is some namespace object) any callable object is acceptable.
Parameters: case_sensitive (bool) – If False (the default), then the results will have each factory twice, once with its found name, and once with its name lower cased. Returns: An iterable of found (name, factory).
Package IO¶
Support for handling the IO for all the objects in a package, typically via a ZCML directive.
-
class
AutoPackageSearchingScopedInterfaceObjectIO
(context, iface_upper_bound=None, validate_after_update=True)[source]¶ Bases:
nti.externalization.datastructures.ModuleScopedInterfaceObjectIO
A special, magic, type of interface-driven input and output, one designed for the common use case of a package that provides the common pattern:
- interfaces.py
- externalization.py (where a subclass of this object lives)
- configure.zcml (where the subclass is registered as an adapter for each object; you may also then provide mime-factories as well)
- other modules, where types are defined for the external interfaces.
Once you derive from this class and implement the abstract methods, you need to call
__class_init__()
(exactly once) on your subclass.You do not have to derive from this class; the common case is handled via the ZCML directive
<ext:registerAutoPackageIO>
(nti.externalization.zcml.IAutoPackageExternalizationDirective
). You can still customize the behaviour by providing theiobase
argument.Parameters: - iface_upper_bound – The upper bound on the schema to use
to externalize
ext_self
; we will use the most derived sub-interface of this interface that the object implements. Subclasses can either override this constructor to pass this parameter (while taking one argument themselves, to be usable as an adapter), or they can define the class attribute_ext_iface_upper_bound
- validate_after_update (bool) – If
True
(the default) then the entire schema will be validated after an object has been updated withupdate_from_external_object()
, not just the keys that were assigned.
-
classmethod
__class_init__
()[source]¶ Class initializer. Should be called exactly once on each distinct subclass.
First, makes all interfaces returned by
_ap_enumerate_externalizable_root_interfaces()
externalizable by setting the__external_class_name__
tagged value (to_ap_compute_external_class_name_from_interface_and_instance()
). (SeeInterfaceObjectIO
.)Then, find all of the object factories and initialize them using
_ap_find_factories()
. A namespace object representing these factories is returned.Changed in version 1.0: Registering the factories using
register_legacy_search_module()
is no longer done by default. If you are using this class outside of ZCML, you will need to subclass and override this method to make that call yourself. If you are using ZCML, you will need to set the appropriate attribute to True.
-
classmethod
_ap_compute_external_class_name_from_concrete_class
(a_type)[source]¶ Return the string value of the external class name.
By default this will be either the value of
__external_class_name__
or, if not found, the value of__name__
.Subclasses may override.
-
classmethod
_ap_compute_external_class_name_from_interface_and_instance
(unused_iface, impl)[source]¶ Assigned as the tagged value
__external_class_name__
to each interface. This will be called on an instance implementing iface.See also
-
classmethod
_ap_compute_external_mimetype
(package_name, unused_a_type, ext_class_name)[source]¶ Return the string value of the external mime type for the given type in the given package having the given external name (probably derived from
_ap_compute_external_class_name_from_concrete_class()
).For example, given the arguments (‘nti.assessment’, FooBar, ‘FooBar’), the result will be ‘application/vnd.nextthought.assessment.foobar’.
Subclasses may override.
-
classmethod
_ap_enumerate_externalizable_root_interfaces
(interfaces)[source]¶ Return an iterable of the root interfaces in this package that should be externalized.
Subclasses must implement.
-
classmethod
_ap_enumerate_module_names
()[source]¶ Return an iterable of module names in this package that should be searched to find factories.
Subclasses must implement.
-
classmethod
_ap_find_factories
(package_name)[source]¶ Return a namespace object whose attribute names are external class names and whose attribute values are classes that can be created externally.
For each module returned by
_ap_enumerate_module_names()
, we will resolve it against the value of package_name (normally that given by_ap_find_package_name()
). The module is then searched for classes that live in that module. If a class implements an interface that has a tagged value of__external_class_name__
, it is added to the return value. The external class name (the name of the attribute) is computed by_ap_compute_external_class_name_from_concrete_class()
.Each class that is found has an appropriate
mimeType
added to it (derived by_ap_compute_external_mimetype()
), if it does not already have one; these classes also have the attribute__external_can_create__
set to true on them if they do not have a value for it at all. This makes the classes ready to be used withregisterMimeFactories()
, which is done automatically by the ZCML directiveIAutoPackageExternalizationDirective
.Each class that is found is also marked as implementing
zope.mimetype.interfaces.IContentTypeAware
.
-
classmethod
_ap_find_package_interface_module
()[source]¶ Return the module that should be searched for interfaces.
By default, this will be the
interfaces
sub-module of the package returned from_ap_find_package_name()
.Subclasses may override.
nti.externalization
¶
The nti.externalization
module has the top-level APIs.
-
to_external_object
(obj, name=<default value>, registry=<default value>, catch_components=(), catch_component_action=None, request=<default value>, decorate=True, useCache=True, decorate_callback=<default value>, default_non_externalizable_replacer=<function DefaultNonExternalizableReplacer>, policy_name=<default value>, policy=<default value>)[source]¶ Translates the object into a form suitable for external distribution, through some data formatting process. See
SEQUENCE_TYPES
andMAPPING_TYPES
for details on what we can handle by default.Parameters: - name (string) – The name of the adapter to
IInternalObjectExternalizer
to look for. Defaults to the empty string (the default adapter). If you provide a name, and an adapter is not found, we will still look for the default name (unless the name you supply is None). - catch_components (tuple) – A tuple of exception classes to catch when externalizing sub-objects (e.g., items in a list or dictionary). If one of these exceptions is caught, then catch_component_action will be called to raise or replace the value. The default is to catch nothing.
- catch_component_action (callable) – If given with
catch_components, a function of two arguments, the object
being externalized and the exception raised. May return a
different object (already externalized) or re-raise the
exception. There is no default, but
catch_replace_action()
is a good choice. - default_non_externalizable_replacer (callable) – If we are
asked to externalize an object and cannot, and there is no
INonExternalizableReplacer
registered for it, then call this object and use the results. - request – If given, the request that the object is being externalized on behalf of. If given, then the object decorators will also look for subscribers to the object plus the request (like traversal adapters); this is a good way to separate out request or user specific code.
- decorate_callback – Callable to be invoked in case there is no decaration
- policy – The
ExternalizationPolicy
to use. Takes priority over policy_name. If this is not given (and neither is policy_name), the thread local state is consulted for the current policy established by the most recent caller to this method; if there is no such caller, then theDEFAULT_EXTERNALIZATION_POLICY
is used. - policy_name (str) – If no policy is given, then this is used to lookup a utility. If this is used, the utility must exist.
- name (string) – The name of the adapter to
-
to_external_representation
(obj, ext_format='json', name=NotGiven) → str[source]¶ Transforms (and returns) the obj into its external (string) representation.
Uses
nti.externalization.to_external_object()
, passing in the name.Parameters: ext_format (str) – One of EXT_REPR_JSON
orEXT_REPR_YAML
, or the name of some other utility that implementsIExternalObjectRepresenter
-
new_from_external_object
(external_object, *args, **kwargs)[source]¶ Like
update_from_external_object
, but creates a new object to update usingfind_factory_for
.All remaining arguments are passed to
update_from_external_object
.If no factory can be found, raises a
zope.interface.interfaces.ComponentLookupError
.Returns the new object.
New in version 1.0a3.
-
update_from_external_object
(containedObject, externalObject, context=None, require_updater=False, notify=True)[source]¶ Central method for updating objects from external values.
Parameters: - containedObject – The object to update.
- externalObject – The object (typically a mapping or sequence) to update the object from. Usually this is obtained by parsing an external format like JSON.
- context – An object passed to the update methods.
- require_updater – If True (not the default) an exception
will be raised if no implementation of
IInternalObjectUpdater
can be found for the containedObject. - notify (bool) – If
True
(the default), then if the updater for the containedObject either has no preference (returns None) or indicates that the object has changed, then anIObjectModifiedFromExternalEvent
will be fired. This may be a recursive process so a top-level call to this object may spawn multiple events. The events that are fired will have adescriptions
list containing one or moreIAttributes
each withattributes
for each attribute we modify (assuming that the keys in theexternalObject
map one-to-one to an attribute; if this is the case and we can also find an interface declaring the attribute, then theIAttributes
will have the right value forinterface
as well). - pre_hook (callable) – If given, called with the before
update_from_external_object is called for every nested object.
Signature
f(k,x)
wherek
is either the key name, or None in the case of a sequence andx
is the external object. Deprecated.
Returns: containedObject after updates from externalObject
Notifies
IObjectModifiedFromExternalEvent
for each object that is modified, andIObjectWillUpdateFromExternalEvent
before doing so.See also
INamedExternalizedObjectFactoryFinder
Changed in version 1.0.0a2: Remove the
object_hook
parameter.Changed in version 1.1.3: Correctly file
IObjectWillUpdateFromExternalEvent
before updating each object.
Strings¶
nti.externalization.integer_strings
: Short readable strings from large integers¶
Functions to represent potentially large integers as the shortest
possible human-readable and writable strings. The motivation is to be
able to take int ids as produced by an zc.intid.IIntId
utility and produce something that can be written down and typed in by
a human. To this end, the strings produced have to be:
- One-to-one and onto the integer domain;
- As short as possible;
- While not being easily confused;
- Or accidentaly permuted
To meet those goals, we define an alphabet consisting of the ASCII digits and upper and lowercase letters, leaving out troublesome pairs (zero and upper and lower oh and upper queue, one and upper and lower ell) (actually, those troublesome pairs will all map to the same character).
We also put a version marker at the end of the string so we can evolve this algorithm gracefully but still honor codes in the wild.
-
to_external_string
(integer)[source]¶ Turn an integer into a native string representation.
>>> from nti.externalization.integer_strings import to_external_string >>> to_external_string(123) 'xk' >>> to_external_string(123456789) 'kVxr5'
-
from_external_string
(key)[source]¶ Turn the string in key into an integer.
>>> from nti.externalization.integer_strings import from_external_string >>> from_external_string('xkr') 6773
Parameters: key (str) – A native string, as produced by
to_external_string
. (On Python 2, unicode keys are also valid.)Raises: - ValueError – If the key is invalid or contains illegal characters.
- UnicodeDecodeError – If the key is a Unicode object, and contains non-ASCII characters (which wouldn’t be valid anyway)
nti.externalization.representation
: Reading and writing objects to strings¶
External representation support.
The provided implementations of
IExternalObjectIO
live here. We
provide and register two, one for JSON
and one for
YAML
.
-
to_external_representation
(obj, ext_format='json', name=NotGiven) → str[source]¶ Transforms (and returns) the obj into its external (string) representation.
Uses
nti.externalization.to_external_object()
, passing in the name.Parameters: ext_format (str) – One of EXT_REPR_JSON
orEXT_REPR_YAML
, or the name of some other utility that implementsIExternalObjectRepresenter
-
to_json_representation
(obj)[source]¶ A convenience function that calls
to_external_representation()
withEXT_REPR_JSON
.
Advanced Customization¶
nti.externalization.extension_points
: Pluggable integrations¶
Extension points for integrating with other applications, frameworks, and libraries.
The normal extension mechanisms for this package are zope.component
and zope.interface
. The particular extension points found here
are different for two reasons:
- There is expected to be only one way that a given application will want to configure the extension point.
- They are believed to be so performance critical to normal operations that the use of a component utility lookup would be noticeably detrimental.
For those two reasons, these extension points are both developed with
zope.hookable
, which provides a very low-overhead way to invoke
a function while allowing for it to be extended. Applications that
need to changed the behaviour of the built-in functions supplied here will
need to call their zope.hookable.hookable.sethook()
method at
startup.
New in version 1.0.
-
get_current_request
() → request¶ In a request/response system like a WSGI server, return an object representing the current request.
In some cases, this may be used to find adapters for objects. It is also passed to the
toExternalObject
function of each object as a keyword parameter.In version 1.0, this will default to using Pyramid’s
pyramid.threadlocal.get_current_request()
if pyramid is installed. However, in a future version, an application wishing to use Pyramid’s request will explicitly need to set the hook.Deprecated since version 1.0: The automatic fallback to Pyramid. It will be removed in 1.1 or before.
-
set_external_identifiers
(self, result) → None¶ Place the stable external identifiers for self in the dictionary result.
By default, this function uses the result of
to_external_oid
as the value to put in result, under the keysOID
as well asNTIID
.This is called from
to_standard_external_dictionary
, a default part of producing the external dictionary for all objects.
nti.externalization.proxy
: Support for transparent proxies¶
Support for working with transparent proxies.
There are times during the externalization process (such as when
computing object identifiers
) that we
need to be working with the “real” object, stripped of any security or
other proxes placed around it. This module provides removeAllProxies
for that purpose.
It is extensible with registerProxyUnwrapper
.
-
removeAllProxies
(proxy)[source]¶ If the object in proxy is proxied by one of the types of proxies known about by this module, remove all of the known proxies, unwrapping down to the original base object.
This module may know about
zope.proxy
,zope.container.contained
, andAcquisition
, if they are installed.>>> from nti.externalization.proxy import removeAllProxies >>> from zope.container.contained import ContainedProxy >>> obj = object() >>> proxy = ContainedProxy(obj) >>> proxy == obj True >>> proxy is obj False >>> removeAllProxies(obj) is obj True >>> removeAllProxies(proxy) is obj True
Changed in version 1.0: The default proxy unwrappers are all optional and will only be registered if they can be imported.
-
registerProxyUnwrapper
(func)[source]¶ Register a function that can unwrap a single proxy from a proxied object. If there is nothing to unwrap, the function should return the given object.
New in version 1.0: This is a provisional way to extend the unwrapping functionality (where speed is critical). It may not be supported in the future.
ZODB Integration¶
nti.externalization.oids
: Stable object identifiers¶
Functions for finding and parsing OIDs.
-
to_external_oid
(self, default=None, add_to_connection=False, add_to_intids=False) → bytes[source]¶ For a
persistent object
, returns itspersistent OID
in a parseable external format (seefrom_external_oid()
). This format includes the database name (so it works in a ZODB multi-database) and the integer ID from the closestzope.intid.interfaces.IIntIds
utility.If the object implements a method
toExternalOID()
, that method will be called and its result (or the default) will be returned. This should generally be considered legacy behaviour.If the object has not been saved, and add_to_connection is
False
(the default) returns the default.Parameters: - add_to_connection (bool) – If the object is persistent but not yet added to a connection, setting this to true will attempt to add it to the nearest connection in its containment tree, thus letting it have an OID.
- add_to_intids (bool) – If we can obtain an OID for this
object, but it does not have an intid, and an intid utility is
available, then if this is
True
(not the default) we will register it with the utility.
Returns: A
bytes
string.
-
from_external_oid
(ext_oid)[source]¶ Given a byte string, as produced by
to_external_oid()
, parses it into its component parts.Parameters: ext_oid (bytes) – As produced by to_external_oid()
. (Text/unicode is also accepted.)Returns: A three-tuple, ParsedOID
. Only the OID is guaranteed to be present; the other fields may be empty (db_name
) orNone
(intid
).
nti.externalization.persistence
: Pickling and ZODB support¶
Classes and functions for dealing with persistence (and pickling) in an external context.
Note
Importing this module monkey-patches the class persistent.wref.WeakRef
to directly implement toExternalObject()
and toExternalOID()
.
It’s not clear why we don’t simply register class adapters for those things,
but we don’t.
-
getPersistentState
(obj)[source]¶ For a
persistent.Persistent
object, returns one of the constants from the persistent module for its state:persistent.CHANGED
andpersistent.UPTODATE
being the most useful.If the object is not Persistent and doesn’t implement a
getPersistentState
method, this method will be pessimistic and assume the object has beenpersistent.CHANGED
.
-
class
PersistentExternalizableDictionary
(**kwargs)[source]¶ Bases:
persistent.mapping.PersistentMapping
,nti.externalization.datastructures.ExternalizableDictionaryMixin
Dictionary mixin that provides
toExternalDictionary()
to return a new dictionary with each value in the dict having been externalized withtoExternalObject()
.Changed in version 1.0: No longer extends
nti.zodb.persistentproperty.PersistentPropertyHolder
. If you have subclasses that use writable properties and which should bypass the normal attribute setter implementation, please mixin this superclass (first) yourself.
-
class
PersistentExternalizableList
(initlist=None)[source]¶ Bases:
persistent.list.PersistentList
List mixin that provides
toExternalList()
to return a new list with each element in the sequence having been externalized withtoExternalObject()
.Changed in version 1.0: No longer extends
nti.zodb.persistentproperty.PersistentPropertyHolder
. If you have subclasses that use writable properties and which should bypass the normal attribute setter implementation, please mixin this superclass (first) yourself.
-
class
PersistentExternalizableWeakList
(initlist=None)[source]¶ Bases:
nti.externalization.persistence.PersistentExternalizableList
Stores
persistent.Persistent
objects as weak references, invisibly to the user. Any weak references added to the list will be treated the same.Weak references are resolved on access; if the referrant has been deleted, then that access will return
None
.Changed in version 1.0: No longer extends
nti.zodb.persistentproperty.PersistentPropertyHolder
. If you have subclasses that use writable properties and which should bypass the normal attribute setter implementation, please mixin this superclass (first) yourself.-
index
(value) → integer -- return first index of value.[source]¶ Raises ValueError if the value is not present.
-
-
NoPickle
(cls)[source]¶ A class decorator that prevents an object from being pickled. Useful for ensuring certain objects do not get pickled and thus avoiding ZODB backward compatibility concerns.
Warning
Subclasses of such decorated classes are also not capable of being pickled, without appropriate overrides of
__reduce_ex__
and__getstate__
. A “working” subclass, but only for ZODB, looks like this:@NoPickle class Root(object): pass class CanPickle(Persistent, Root): pass
Warning
This decorator emits a warning when it is used directly on a
Persistent
subclass, as that rather defeats the point (but it is still allowed for backwards compatibility).
Helpers¶
nti.externalization.factory
: Object factories¶
Implementations of object factories.
New in version 1.0.
-
class
ObjectFactory
(callable=None, title='', description='', interfaces=None)[source]¶ Bases:
zope.component.factory.Factory
A convenient
zope.component.interfaces.IFactory
meant to be registered as a named utility.The callable object SHOULD be a type/class object, because that’s the only thing we base equality off of (class identity).
This class can be subclassed and trivially instantiated by setting class properties
default_factory
and (optionally)default_title
anddefault_description
. Constructor arguments will override these, but if not given, the class values will be used.For example:
>>> class MyObjectFactory(ObjectFactory): ... default_factory = object ... default_title = 'A Title' ... default_description = 'A Description' >>> factory = MyObjectFactory() >>> factory <MyObjectFactory titled 'A Title' using <... 'object'> producing []> >>> print(factory.title) A Title >>> print(factory.description) A Description
-
default_description
= u''¶ The default description, if none is given to the constructor.
-
default_factory
= None¶ The default callable argument, if none is given to the constructor.
-
default_title
= u''¶ The default title, if none is given to the constructor.
-
-
class
MimeObjectFactory
(callable=None, title='', description='', interfaces=None)[source]¶ Bases:
nti.externalization.factory.ObjectFactory
Default implementation of
IMimeObjectFactory
.
-
class
ClassObjectFactory
(callable=None, title='', description='', interfaces=None)[source]¶ Bases:
nti.externalization.factory.ObjectFactory
Default implementation of
IClassObjectFactory
.
-
class
AnonymousObjectFactory
(callable=None, title='', description='', interfaces=None)[source]¶ Bases:
nti.externalization.factory.ObjectFactory
Default implementation of
IAnonymousObjectFactory
.New in version 1.0a3.
Datetime¶
Support for reading, writing and converting date and time related objects.
See the datetime
module, as well as the
zope.interface.common.idatetime
module for types of objects.
These are generally meant to be used as zope.interface adapters once this package has been configured, but they can be called manually as well.
-
class
date_to_string
(date)[source]¶ Bases:
object
Produce an IOS8601 string from a date.
Registered as an adapter from
zope.interface.common.idatetime.IDate
toIInternalObjectExternalizer
.>>> import datetime >>> from nti.externalization.externalization import to_external_object >>> from nti.externalization.datetime import date_to_string >>> from zope import component >>> component.provideAdapter(date_to_string) >>> to_external_object(datetime.date(1982, 1, 31)) '1982-01-31'
-
date_from_string
(string)[source]¶ This adapter allows any field which comes in as a string in IOS8601 format to be transformed into a date. The schema field must be an
zope.schema.Object
field with a type ofzope.interface.common.idatetime.IDate
.If you need a schema field that accepts human input, rather than programattic input, you probably want to use a custom field that uses
zope.datetime.parse()
in itsfromUnicode
method.>>> from nti.externalization.datetime import date_from_string >>> date_from_string('1982-01-31') datetime.date(1982, 1, 31)
-
class
datetime_to_string
(date)[source]¶ Bases:
object
Produce an IOS8601 string from a datetime.
Registered as an adapter from
zope.interface.common.idatetime.IDateTime
toIInternalObjectExternalizer
.>>> from zope.component import provideAdapter >>> import datetime >>> from nti.externalization import to_external_object >>> from nti.externalization.datetime import datetime_to_string >>> provideAdapter(datetime_to_string) >>> to_external_object(datetime.datetime(1982, 1, 31)) '1982-01-31T00:00:00Z'
-
datetime_from_string
(string, assume_local=False, local_tzname=None)[source]¶ This adapter allows any field which comes in as a string in IOS8601 format to be transformed into a
datetime.datetime
. The schema field should be annti.schema.field.Object
field with a type ofzope.interface.common.idatetime.IDateTime
or an instance ofnti.schema.field.ValidDateTime
. Wrap this with annti.schema.fieldproperty.AdaptingFieldProperty
.Datetime values produced by this object will always be in GMT/UTC time, and they will always be datetime naive objects.
If you need a schema field that accepts human input, rather than programattic input, you probably want to use a custom field that uses
zope.datetime.parse()
in itsfromUnicode
method.When used as an adapter, no parameters are accepted.
>>> from zope.interface.common.idatetime import IDateTime >>> from zope.component import provideAdapter >>> from nti.externalization.datetime import datetime_from_string >>> provideAdapter(datetime_from_string) >>> IDateTime('1982-01-31T00:00:00Z') datetime.datetime(1982, 1, 31, 0, 0)
Parameters: - assume_local (bool) – If
False
, the default, then when we parse a string that does not include timezone information, we will assume that it is already meant to be in UTC. Otherwise, if set to true, when we parse such a string we will assume that it is meant to be in the “local” timezone and adjust accordingly. If the local timezone experiences DST, then the time will be interpreted with the UTC offset as-of the DST rule in effect on the date parsed, not the current date, if possible. If not possible, the current rule will be used. - local_tzname (str) – If given, either a string acceptable to
pytz.timezone()
to produce atzinfo
object, or a two-tuple as given fromtime.timezone
. If not given, local timezone will be determined automatically.
- assume_local (bool) – If
-
datetime_from_timestamp
(value)[source]¶ Produce a
datetime.datetime
from a UTC timestamp.This is a registered adapter for both integers and floats.
>>> from zope.interface.common.idatetime import IDateTime >>> from zope.component import provideAdapter >>> from nti.externalization.datetime import datetime_from_timestamp >>> provideAdapter(datetime_from_timestamp, (int,)) >>> provideAdapter(datetime_from_timestamp, (float,)) >>> IDateTime(123456) datetime.datetime(1970, 1, 2, 10, 17, 36) >>> IDateTime(654321.0) datetime.datetime(1970, 1, 8, 13, 45, 21)
-
class
duration_to_string
(date)[source]¶ Bases:
object
Produce an IOS8601 format duration from a
datetime.timedelta
object.Timedelta objects do not represent years or months (the biggest duration they accept is weeks) and internally they normalize everything to days and smaller. Thus, the format produced by this transformation will never have a field larger than days.
Registered as an adapter from
zope.interface.common.idatetime.ITimeDelta
toIInternalObjectExternalizer
.>>> import datetime >>> from zope.component import provideAdapter >>> from nti.externalization import to_external_object >>> from nti.externalization.datetime import duration_to_string >>> provideAdapter(duration_to_string) >>> to_external_object(datetime.timedelta(weeks=16)) 'P112D'
-
duration_from_string
(value)[source]¶ Produce a
datetime.timedelta
from a ISO8601 format duration string.>>> from zope.interface.common.idatetime import ITimeDelta >>> from zope.component import provideAdapter >>> from nti.externalization.datetime import duration_from_string >>> provideAdapter(duration_from_string) >>> ITimeDelta('P0D') datetime.timedelta(0)
Dublincore¶
Externalization support for things that implement the interfaces
of zope.dublincore.interfaces
.
Note
We are “namespacing” the dublincore properties, since they have defined meanings we don’t control. We are currently doing this by simply prefixing them with ‘DC’ for ease of access in JavaScript.
These objects are typically used as decorators, registered from ZCML.
-
class
DCExtendedExternalMappingDecorator
[source]¶ Bases:
nti.externalization.singleton.Singleton
Adds the extended properties of dublincore to external objects as defined by
zope.dublincore.interfaces.IDCExtended
.Note
We are currently only mapping ‘Creator’ since that’s the only field that ever gets populated.
Implements
IExternalStandardDictionaryDecorator
forzope.dublincore.interfaces.IDCExtended
objects.
-
class
DCDescriptivePropertiesExternalMappingDecorator
[source]¶ Bases:
nti.externalization.singleton.Singleton
Supports the ‘DCTitle’ and ‘DCDescription’ fields, as defined in
zope.dublincore.interfaces.IDCDescriptiveProperties
.Implements
IExternalStandardDictionaryDecorator
.
Singleton¶
Support for fast, memory efficient singleton objects.
Why is this here? The externalization system can use lots of objects as it goes through its process. Many of those objects are adapters (for example, the decorator objectes), meaning a factory callable is called to (create and) return an object (given a particular context, and possibly request).
But the API of many adapter objects accept all the information they need to have in the functions defined in the interface. That is, the constructor does nothing useful with the context (and request). The objects are stateless, and so constructing a new one for each adapter invocation can be a waste of time and memory.
By either using the SingletonMetaclass
as your metaclass, or
subclassing Singleton
, that cost is paid only once, replacing a call
to a constructor and an object allocation with a faster call to return
a constant object.
-
class
SingletonMetaclass
[source]¶ Bases:
type
Metaclass for singleton classes most commonly used as external object decorators (adapters). These objects accept one or two context arguments to their
__init__
function, but they do not actually use them (because the same object is passed to their decoration method). Thus they can usefully be constructed just once and shared. This metaclass ensures the singleton property, ensures that the__init__
method is a no-op, and ensures that the instance has no dictionary or instance variable.A singleton class has only one instance which is returned every time the class is instantiated.
Caution
We cannot be used with
six.with_metaclass()
because it introduces temporary classes. You’ll need to use the metaclass constructor directly:AClass = SingletonMetaclass('AClass', (object,), {})
Alternatively, you can inherit from
Singleton
.Implementation Notes
The class is instantiated immediately at the point where it is defined by calling
cls.__new__(cls)
. This instance is cached andcls.__new__
is rebound to return it directly.The original constructor is also cached to allow subclasses to access it and have their own instance.
>>> from nti.externalization.singleton import Singleton >>> class TopSingleton(Singleton): ... def __init__(self): ... print("I am never called") >>> inst = TopSingleton() >>> isinstance(inst, TopSingleton) True >>> TopSingleton() is inst True >>> class DerivedSingelton(TopSingleton): ... pass >>> derived = DerivedSingelton() >>> isinstance(derived, DerivedSingelton) True >>> DerivedSingelton() is derived True >>> derived is inst False