Skip to content

Commit

Permalink
Add default public implementations of IMime/ClassObjectFactory.
Browse files Browse the repository at this point in the history
  • Loading branch information
jamadden committed Sep 29, 2017
1 parent e24f34d commit 7d9e10c
Show file tree
Hide file tree
Showing 5 changed files with 230 additions and 26 deletions.
2 changes: 2 additions & 0 deletions CHANGES.rst
Expand Up @@ -40,3 +40,5 @@
``zope.preference``, please include it 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`` and
``IClassObjectFactory`` in ``nti.externalization.factory``.
5 changes: 5 additions & 0 deletions docs/api.rst
Expand Up @@ -7,6 +7,11 @@ Interfaces

.. automodule:: nti.externalization.interfaces

Factories
=========

.. automodule:: nti.externalization.factory

Utilities
=========

Expand Down
93 changes: 93 additions & 0 deletions src/nti/externalization/factory.py
@@ -0,0 +1,93 @@
# -*- coding: utf-8 -*-
"""
Implementations of object factories.
.. versionadded:: 1.0
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

from zope import interface
from zope.component.factory import Factory

from nti.externalization.interfaces import IClassObjectFactory
from nti.externalization.interfaces import IMimeObjectFactory

_builtin_callable = callable

# pylint: disable=redefined-builtin, protected-access

class ObjectFactory(Factory):
"""
A convenient :class:`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 :attr:`default_factory` and (optionally)
:attr:`default_title` and :attr:`default_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 # doctest: +ELLIPSIS
<MyObjectFactory for <... 'object'>>
>>> print(factory.title)
A Title
>>> print(factory.description)
A Description
"""

#: The default callable argument, if none is given to the
#: constructor.
default_factory = None
#: The default title, if none is given to the constructor.
default_title = u''
#: The default description, if none is given to the constructor.
default_description = u''

def __init__(self, callable=None, title='', description='', interfaces=None):
callable = callable if callable is not None else self.default_factory
if callable is None or not _builtin_callable(callable):
raise ValueError("Must provide callable object, not %r" % (callable,))
Factory.__init__(self,
callable,
title or self.default_title,
description or self.default_description,
interfaces)

def __eq__(self, other):
# Implementing equality is needed to prevent multiple inclusions
# of the same module from different places from conflicting.
try:
return self._callable is other._callable
except AttributeError: # pragma: no cover
return NotImplemented

def __hash__(self):
return hash(self._callable)


@interface.implementer(IMimeObjectFactory)
class MimeObjectFactory(ObjectFactory):
"""
Default implementation of
:class:`nti.externalization.interfaces.IMimeObjectFactory`.
"""

@interface.implementer(IClassObjectFactory)
class ClassObjectFactory(ObjectFactory):
"""
Default implementation of
:class:`nti.externalization.interfaces.IClassObjectFactory`.
"""
122 changes: 122 additions & 0 deletions src/nti/externalization/tests/test_factory.py
@@ -0,0 +1,122 @@
# -*- coding: utf-8 -*-
"""
Tests for factory.py
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

# stdlib imports
import doctest
import unittest

from nti.testing import base
from nti.testing import matchers

from hamcrest import assert_that
from hamcrest import has_entry
from hamcrest import has_key
from hamcrest import is_
from hamcrest import is_not
from hamcrest import has_property
from nti.testing.matchers import validly_provides

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

# disable: accessing protected members, too many methods
# pylint: disable=W0212,R0904

class TestObjectFactory(unittest.TestCase):

def _getInterface(self):
from zope.component.interfaces import IFactory
return IFactory

def _getTargetClass(self):
from nti.externalization.factory import ObjectFactory
return ObjectFactory

def _makeOne(self, *args, **kwargs):
return self._getTargetClass()(*args, **kwargs)

def test_implements_interface(self):

class Callable(object):
pass

factory = self._makeOne(Callable)

assert_that(factory(), is_(Callable))
assert_that(factory.title, is_(''))
assert_that(factory.description, is_(''))

assert_that(factory, validly_provides(self._getInterface()))

def test_equality(self):

class Callable1(object):
pass

factory1 = self._makeOne(Callable1)
factory1_a = self._makeOne(Callable1)

assert_that(factory1, is_(factory1_a))

class Callable2(object):
pass

factory2 = self._makeOne(Callable2)
assert_that(factory1, is_not(factory2))
assert_that(factory2, is_not(factory1))

def test_subclass(self):
class Callable(object):
pass

class Factory(self._getTargetClass()):
default_factory = Callable
default_title = "title"
default_description = "description"

factory = Factory()

assert_that(factory, has_property('_callable', Callable))
assert_that(factory, has_property('title', Factory.default_title))
assert_that(factory, has_property('description', Factory.default_description))

factory = Factory(object, 'foo', 'baz')
assert_that(factory, has_property('_callable', object))
assert_that(factory, has_property('title', 'foo'))
assert_that(factory, has_property('description', 'baz'))

def test_create_non_callable(self):
with self.assertRaises(ValueError):
self._makeOne()


class TestMimeObjectFactory(TestObjectFactory):

def _getInterface(self):
from nti.externalization.interfaces import IMimeObjectFactory
return IMimeObjectFactory

def _getTargetClass(self):
from nti.externalization.factory import MimeObjectFactory
return MimeObjectFactory

class TestClassObjectFactory(TestObjectFactory):

def _getInterface(self):
from nti.externalization.interfaces import IClassObjectFactory
return IClassObjectFactory

def _getTargetClass(self):
from nti.externalization.factory import ClassObjectFactory
return ClassObjectFactory

def test_suite():
return unittest.TestSuite((
unittest.defaultTestLoader.loadTestsFromName(__name__),
doctest.DocTestSuite("nti.externalization.factory"),
))
34 changes: 8 additions & 26 deletions src/nti/externalization/zcml.py
Expand Up @@ -13,41 +13,23 @@
from ZODB import loglevels
from zope import interface
from zope.component import zcml as component_zcml
from zope.component.factory import Factory
from zope.configuration.fields import Bool
from zope.configuration.fields import GlobalInterface
from zope.configuration.fields import GlobalObject
from zope.configuration.fields import Tokens

from nti.externalization import internalization
from nti.externalization.autopackage import AutoPackageSearchingScopedInterfaceObjectIO
from nti.externalization.interfaces import IMimeObjectFactory
from nti.externalization.internalization import _find_factories_in_module
from . import internalization
from .autopackage import AutoPackageSearchingScopedInterfaceObjectIO
from .factory import MimeObjectFactory
from .interfaces import IMimeObjectFactory
from .internalization import _find_factories_in_module

__docformat__ = "restructuredtext en"

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

# pylint: disable=protected-access,inherit-non-class

@interface.implementer(IMimeObjectFactory)
class _MimeObjectFactory(Factory):
"""
A factory 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).
"""

def __eq__(self, other):
# Implementing equality is needed to prevent multiple inclusions
# of the same module from different places from conflicting.
try:
return self._callable is other._callable
except AttributeError: # pragma: no cover
return NotImplemented

def __hash__(self):
return hash(self._callable)


class IRegisterInternalizationMimeFactoriesDirective(interface.Interface):
Expand Down Expand Up @@ -88,9 +70,9 @@ def registerMimeFactories(_context, module):
logger.log(loglevels.TRACE,
"Registered mime factory utility %s = %s (%s)",
object_name, value, mime_type)
factory = _MimeObjectFactory(value,
title=object_name,
interfaces=list(interface.implementedBy(value)))
factory = MimeObjectFactory(value,
title=object_name,
interfaces=list(interface.implementedBy(value)))
component_zcml.utility(_context,
provides=IMimeObjectFactory,
component=factory,
Expand Down

0 comments on commit 7d9e10c

Please sign in to comment.