Skip to content

Commit

Permalink
Merge branch 'feature.editable'
Browse files Browse the repository at this point in the history
  • Loading branch information
mcdonc committed Sep 3, 2013
2 parents 4d2d77b + 1e6f8f5 commit bf103c3
Show file tree
Hide file tree
Showing 4 changed files with 218 additions and 0 deletions.
13 changes: 13 additions & 0 deletions docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,19 @@ Other Helpers

.. autofunction:: includeme

:mod:`substanced.editable` API
------------------------------

.. automodule:: substanced.editable

.. autointerface:: IEditable
:members:
:inherited-members:

.. autoclass:: FileEditable

.. autofunction:: register_editable_adapter

:mod:`substanced.event` API
---------------------------

Expand Down
1 change: 1 addition & 0 deletions substanced/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ def include(config): # pragma: no cover
config.include('.dump')
config.include('.locking')
config.include('.audit')
config.include('.editable')

def scan(config): # pragma: no cover
""" Perform all ``config.scan`` tasks required for Substance D and the
Expand Down
74 changes: 74 additions & 0 deletions substanced/editable/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
from zope.interface import (
Interface,
implementer,
)

from ..file import IFile
from ..util import chunks

class IEditable(Interface):
""" Adapter interface for editing content as a file.
"""
def get():
""" Return ``(body_iter, mimetype)`` representing the context.
- ``body_iter`` is an iterable, whose chunks are bytes represenating
the context as an editable file.
- ``mimetype`` is the MIMEType corresponding to ``body_iter``.
"""

def put(fileish):
""" Update context based on the contents of ``fileish``.
- ``fileish`` is a file-type object: its ``read`` method should
return the (new) file representation of the context.
"""

@implementer(IEditable)
class FileEditable(object):
""" IEditable adapter for stock SubstanceD 'File' objects.
"""
def __init__(self, context, request):
self.context = context
self.request = request

def get(self):
""" See IEditable.
"""
return (
chunks(open(self.context.blob.committed(), 'rb')),
self.context.mimetype or 'application/octet-stream',
)

def put(self, fp):
""" See IEditable.
"""
self.context.upload(fp)

def register_editable_adapter(config, adapter, iface): # pragma: no cover
""" Configuration directive: register ``IEditable`` adapter for ``iface``.
- ``adapter`` is the adapter factory (a class or other callable taking
``(context, request)``).
- ``iface`` is the interface / class for which the adapter is registerd.
"""
def register():
intr['registered'] = adapter
config.registry.registerAdapter(adapter, (iface, Interface), IEditable)

discriminator = ('sd-editable-adapter', iface)
intr = config.introspectable(
'sd editable adapters',
discriminator,
iface.__name__,
'sd editable adapter'
)
intr['adapter'] = adapter

config.action(discriminator, callable=register, introspectables=(intr,))

def includeme(config): # pragma: no cover
config.add_directive('register_editable_adapter', register_editable_adapter)
config.register_editable_adapter(FileEditable, IFile)
130 changes: 130 additions & 0 deletions substanced/editable/tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import unittest

class TestFileEditable(unittest.TestCase):

def _getTargetClass(self):
from . import FileEditable
return FileEditable

def _makeOne(self, context=None, request=None):
if context is None:
context = object()
if request is None:
request = object()
return self._getTargetClass()(context, request)

def test_class_conforms_to_IEditable(self):
from zope.interface.verify import verifyClass
from . import IEditable
verifyClass(IEditable, self._getTargetClass())

def test_instance_conforms_to_IEditable(self):
from zope.interface.verify import verifyObject
from . import IEditable
verifyObject(IEditable, self._makeOne())

def test_get_context_has_mimetype(self):
from pyramid.testing import DummyRequest
from pyramid.testing import DummyResource
context = DummyResource()
context.mimetype = 'application/foo'
blob = DummyResource()
here = __file__
def committed():
return here
blob.committed = committed
context.blob = blob
request = DummyRequest()
inst = self._makeOne(context, request)
iterable, mimetype = inst.get()
self.assertEqual(mimetype, 'application/foo')
self.assertEqual(type(next(iterable)), bytes)

def test_get_context_has_no_mimetype(self):
from pyramid.testing import DummyRequest
from pyramid.testing import DummyResource
context = DummyResource()
context.mimetype = None
blob = DummyResource()
here = __file__
def committed():
return here
blob.committed = committed
context.blob = blob
request = DummyRequest()
inst = self._makeOne(context, request)
iterable, mimetype = inst.get()
self.assertEqual(mimetype, 'application/octet-stream')
self.assertEqual(type(next(iterable)), bytes)

def test_put(self):
from pyramid.testing import DummyRequest
from pyramid.testing import DummyResource
context = DummyResource()
fp = 'fp'
def upload(_fp):
self.assertEqual(_fp, fp)
context.upload = upload
request = DummyRequest()
inst = self._makeOne(context, request)
inst.put(fp)

class Test_register_editable_adapter(unittest.TestCase):

def setUp(self):
from pyramid.testing import setUp
self.config = setUp()

def tearDown(self):
from pyramid.testing import tearDown
tearDown()

def _callFUT(self, config, adapter, iface):
from . import register_editable_adapter
return register_editable_adapter(config, adapter, iface)

def test_it(self):
from zope.interface import Interface
from . import IEditable
class ITesting(Interface):
pass
config = DummyConfigurator(self.config.registry)
def _editable_factory(context, reqeust): #pragma NO COVER
pass
self._callFUT(config, _editable_factory, ITesting)
self.assertEqual(len(config.actions), 1)
action = config.actions[0]
self.assertEqual(action['discriminator'],
('sd-editable-adapter', ITesting))
self.assertEqual(
action['introspectables'], (config.intr,)
)
callable = action['callable']
callable()
wrapper = self.config.registry.adapters.lookup(
(ITesting, Interface), IEditable)
self.assertEqual(config.intr['registered'], wrapper)

class DummyIntrospectable(dict):
pass

class DummyConfigurator(object):
_ainfo = None
def __init__(self, registry):
self.actions = []
self.intr = DummyIntrospectable()
self.registry = registry
self.indexes = []

def action(self, discriminator, callable, order=None, introspectables=()):
self.actions.append(
{
'discriminator':discriminator,
'callable':callable,
'order':order,
'introspectables':introspectables,
})

def introspectable(self, category, discriminator, name, single):
return self.intr

0 comments on commit bf103c3

Please sign in to comment.