Skip to content

Commit

Permalink
100% coverage for autopackage.py
Browse files Browse the repository at this point in the history
  • Loading branch information
jamadden committed Sep 21, 2017
1 parent 39e127a commit efde4cc
Show file tree
Hide file tree
Showing 5 changed files with 198 additions and 54 deletions.
1 change: 1 addition & 0 deletions docs/api.rst
Expand Up @@ -78,3 +78,4 @@ Package IO

.. automodule:: nti.externalization.autopackage
:private-members:
:special-members:
106 changes: 54 additions & 52 deletions src/nti/externalization/autopackage.py
Expand Up @@ -50,6 +50,8 @@ def _ap_compute_external_class_name_from_interface_and_instance(cls, unused_ifac
"""
Assigned as the tagged value ``__external_class_name__`` to each
interface. This will be called on an instance implementing iface.
.. seealso:: :class:`~.InterfaceObjectIO`
"""
# Use the __class__, not type(), to work with proxies
return cls._ap_compute_external_class_name_from_concrete_class(impl.__class__)
Expand Down Expand Up @@ -143,58 +145,58 @@ class _ClassNameRegistry(object):
for _, v in mod.__dict__.items():
# ignore imports and non-concrete classes
# NOTE: using issubclass to properly support metaclasses
if getattr(v, '__module__', None) != mod.__name__ \
if getattr(v, '__module__', None) != mod.__name__ \
or not issubclass(type(v), type):
continue
# implementation_name = k
implementation_class = v
# Does this implement something that should be externalizable?
check_ext = any(iface.queryTaggedValue('__external_class_name__')
for iface in interface.implementedBy(implementation_class))
if check_ext:
ext_class_name = cls._ap_compute_external_class_name_from_concrete_class(implementation_class)

setattr(_ClassNameRegistry,
ext_class_name,
implementation_class)

if not implementation_class.__dict__.get('mimeType', None):
# NOT hasattr. We don't use hasattr because inheritance would
# throw us off. It could be something we added, and iteration order
# is not defined (if we got the subclass first we're good, we fail if we
# got superclass first).
# Also, not a simple 'in' check. We want to allow for setting mimeType = None
# in the dict for static analysis purposes

# legacy check
if 'mime_type' in implementation_class.__dict__:
setattr(implementation_class, 'mimeType',
implementation_class.__dict__['mime_type'])
else:
setattr(implementation_class,
'mimeType',
cls._ap_compute_external_mimetype(package_name,
implementation_class,
ext_class_name))
setattr(implementation_class,
'mime_type',
implementation_class.mimeType)

# well it does now
if not IContentTypeAware.implementedBy(implementation_class):
interface.classImplements(implementation_class,
IContentTypeAware)

# Opt in for creating, unless explicitly disallowed
if not hasattr(implementation_class, '__external_can_create__'):
setattr(implementation_class,
'__external_can_create__',
True)
# Let them have containers
if not hasattr(implementation_class, 'containerId'):
setattr(implementation_class, 'containerId', None)
cls._ap_handle_one_potential_factory_class(_ClassNameRegistry, package_name, v)
return _ClassNameRegistry

@classmethod
def _ap_handle_one_potential_factory_class(cls, namespace, package_name, implementation_class):
# Private helper function
# Does this implement something that should be externalizable?
check_ext = any(iface.queryTaggedValue('__external_class_name__')
for iface in interface.implementedBy(implementation_class))
if not check_ext:
return

ext_class_name = cls._ap_compute_external_class_name_from_concrete_class(implementation_class)

setattr(namespace,
ext_class_name,
implementation_class)

if not implementation_class.__dict__.get('mimeType', None):
# NOT hasattr. We don't use hasattr because inheritance would
# throw us off. It could be something we added, and iteration order
# is not defined (if we got the subclass first we're good, we fail if we
# got superclass first).
# Also, not a simple 'in' check. We want to allow for setting mimeType = None
# in the dict for static analysis purposes

# legacy check
if 'mime_type' in implementation_class.__dict__:
implementation_class.mimeType = implementation_class.mime_type
else:
implementation_class.mimeType = cls._ap_compute_external_mimetype(
package_name,
implementation_class,
ext_class_name)
implementation_class.mime_type = implementation_class.mimeType

if not IContentTypeAware.implementedBy(implementation_class):
# well it does now
interface.classImplements(implementation_class,
IContentTypeAware)

# Opt in for creating, unless explicitly disallowed
if not hasattr(implementation_class, '__external_can_create__'):
implementation_class.__external_can_create__ = True

# Let them have containers
if not hasattr(implementation_class, 'containerId'):
implementation_class.containerId = None

@classmethod
def _ap_find_package_name(cls):
"""
Expand Down Expand Up @@ -223,8 +225,7 @@ def _ap_find_package_interface_module(cls):
package_name = cls._ap_find_package_name()

# Now the interfaces
package_ifaces = dottedname.resolve(package_name + '.interfaces')
return package_ifaces
return dottedname.resolve(package_name + '.interfaces')

@classmethod
def __class_init__(cls): # ExtensionClass.Base class initializer
Expand All @@ -236,6 +237,7 @@ def __class_init__(cls): # ExtensionClass.Base class initializer
:func:`_ap_enumerate_externalizable_root_interfaces`
externalizable by setting the ``__external_class_name__``
tagged value (to :func:`_ap_compute_external_class_name_from_interface_and_instance`).
(See :class:`~.InterfaceObjectIO`.)
Then, find all of the object factories and initialize them
using :func:`_ap_find_factories`. Finally, the namespace object that was
Expand All @@ -245,9 +247,9 @@ def __class_init__(cls): # ExtensionClass.Base class initializer
and disabled by default.
"""
# Do nothing when this class itself is initted
if cls.__name__ == 'AutoPackageSearchingScopedInterfaceObjectIO' \
if cls.__name__ == 'AutoPackageSearchingScopedInterfaceObjectIO' \
and cls.__module__ == __name__:
return
return False

# First, get the correct working module
package_name = cls._ap_find_package_name()
Expand Down
139 changes: 139 additions & 0 deletions src/nti/externalization/tests/test_autopackage.py
@@ -0,0 +1,139 @@
# -*- coding: utf-8 -*-
"""
Tests for autopackage.py
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

# stdlib imports
import unittest

from zope import interface
from zope.mimetype.interfaces import IContentTypeAware

from nti.testing.matchers import implements

from ..autopackage import AutoPackageSearchingScopedInterfaceObjectIO as AutoPackage

from hamcrest import assert_that
from hamcrest import has_key
from hamcrest import has_property
from hamcrest import is_
from hamcrest import is_not as does_not
from hamcrest import none

# disable: accessing protected members, too many methods
# pylint: disable=W0212,R0904
# pylint: disable=no-member
# pylint: disable=inherit-non-class

class TestAutoPackageIO(unittest.TestCase):

def test_init_itself_does_nothing(self):
self.assertIs(AutoPackage.__class_init__(), False)
self.assertIs(AutoPackage.__class_init__(), False)

def test_external_class_name(self):
assert_that(AutoPackage._ap_compute_external_class_name_from_interface_and_instance(None,
self),
is_(type(self).__name__))

class Foo(object):
__external_class_name__ = 'Biz'

assert_that(AutoPackage._ap_compute_external_class_name_from_interface_and_instance(None,
Foo()),
is_(Foo.__external_class_name__))

def test_external_mimetype(self):

assert_that(AutoPackage._ap_compute_external_mimetype('nti.externalization.tests',
None,
'AClassName'),
is_('application/vnd.nextthought.tests.aclassname'))

def test_find_package_name(self):
assert_that(AutoPackage._ap_find_package_name(),
is_('nti.externalization'))

def test_find_interfaces(self):
from nti.externalization import interfaces
assert_that(AutoPackage._ap_find_package_interface_module(),
is_(interfaces))

def test_assigns_mimeType_and_mime_type(self):

class IExt(interface.Interface):
interface.taggedValue('__external_class_name__', 'Ext')

@interface.implementer(IExt)
class E(object):
pass

AutoPackage._ap_handle_one_potential_factory_class(E, 'nti.package', E)

assert_that(E, has_property("mimeType", 'application/vnd.nextthought.package.e'))
assert_that(E, has_property("mime_type", 'application/vnd.nextthought.package.e'))
assert_that(E, has_property("containerId", none()))
assert_that(E, has_property('__external_can_create__', True))
assert_that(E, implements(IContentTypeAware))
assert_that(E, has_property('E', E))
del E.E

def test_copies_mimeType_from_mime_type(self):

class IExt(interface.Interface):
interface.taggedValue('__external_class_name__', 'Ext')

@interface.implementer(IExt)
class E(object):
mime_type = None

AutoPackage._ap_handle_one_potential_factory_class(E, 'nti.package', E)

assert_that(E, has_property("mimeType", none()))
assert_that(E, has_property("mime_type", none()))
del E.E

@interface.implementer(IExt)
class F(object):
mime_type = 'app/mime'

AutoPackage._ap_handle_one_potential_factory_class(F, 'nti.package', F)

assert_that(F, has_property("mimeType", 'app/mime'))
assert_that(F, has_property("mime_type", 'app/mime'))
del F.F

def test_does_nothing_if_mimeType_present(self):
class IExt(interface.Interface):
interface.taggedValue('__external_class_name__', 'Ext')

@interface.implementer(IExt)
class E(object):
mimeType = 'foo'

AutoPackage._ap_handle_one_potential_factory_class(E, 'nti.package', E)
assert_that(E, has_property("mimeType", 'foo'))
assert_that(E, does_not(has_property("mime_type")))
assert_that(E, does_not(implements(IContentTypeAware)))
del E.E

def test_inherits_external_can_create(self):
class IExt(interface.Interface):
interface.taggedValue('__external_class_name__', 'Ext')

class Base(object):
__external_can_create__ = False

@interface.implementer(IExt)
class E(Base):
pass

AutoPackage._ap_handle_one_potential_factory_class(E, 'nti.package', E)

assert_that(E, has_property('__external_can_create__', False))

assert_that(E.__dict__, does_not(has_key('__external_can_create__')))
4 changes: 3 additions & 1 deletion src/nti/externalization/zcml.py
Expand Up @@ -149,13 +149,15 @@ def autoPackageExternalization(_context, root_interfaces, modules,
ext_module_name = root_interfaces[0].__module__
package_name = ext_module_name.rsplit('.', 1)[0]

root_interfaces = frozenset(root_interfaces)
@classmethod
def _ap_enumerate_externalizable_root_interfaces(cls, unused_ifaces):
return root_interfaces

module_names = frozenset([m.__name__.split('.')[-1] for m in modules])
@classmethod
def _ap_enumerate_module_names(cls):
return [m.__name__.split('.')[-1] for m in modules]
return module_names

@classmethod
def _ap_find_package_name(cls):
Expand Down
2 changes: 1 addition & 1 deletion tox.ini
Expand Up @@ -14,7 +14,7 @@ basepython =
python2.7
commands =
coverage run -m zope.testrunner --test-path=src
coverage report --fail-under=85
coverage report --fail-under=95
deps =
{[testenv]deps}
coverage
Expand Down

0 comments on commit efde4cc

Please sign in to comment.