forked from Pylons/substanced
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
218 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
|