Skip to content

Latest commit

 

History

History
233 lines (165 loc) · 7 KB

dev_manual.rst

File metadata and controls

233 lines (165 loc) · 7 KB

Portal Transforms' Developer manual

Author: Sylvain Thenault
Contact: syt@logilab.fr
class isourceAdapter(Interface):

    def __call__(data, **kwargs):
        """convert data to unicode, may take optional kwargs to aid in conversion"""

class imimetypes_registry(isourceAdapter):

    def classify(data, mimetype=None, filename=None):
        """return a content type for this data or None
        None should rarely be returned as application/octet can be
        used to represent most types.
        """

    def lookup(mimetypestring):
        """Lookup for imimetypes object matching mimetypestring.
        mimetypestring may have an empty minor part or containing a wildcard (*).
        mimetypestring may be an imimetype object (in this case it will be
        returned unchanged, else it should be a RFC-2046 name.
        Return a list of mimetypes objects associated with the RFC-2046 name.
        Return an empty list if no one is known.
        """

    def lookupExtension(filename):
        """ return the mimetypes object associated with the file's extension
        or None if it is not known.
        filename maybe a file name like 'content.txt' or an extension like 'rest'
        """

    def mimetypes():
        """return all defined mime types, each one implements at least imimetype
        """

    def list_mimetypes():
        """return all defined mime types, as string"""
class IEngine(Interface):

    def registerTransform(transform):
        """register a transform
        transform must implements itransform
        """

    def unregisterTransform(name):
        """ unregister a transform
        name is the name of a registered transform
        """

    def convertTo(mimetype, orig, idata=None, **kwargs):
        """Convert orig to a given mimetype
        return an object implementing idatastream or None if not path has been
        found
        """

    def convert(name, orig, idata=None, **kwargs):
        """run a tranform of a given name on data
        name is the name of a registered transform
        return an object implementing idatastream
        """

    def __call__(name, orig, idata=None, **kwargs):
        """run a transform returning the raw data product
        name is the name of a registered transform
        return an object implementing idatastream
        """

Writing a new transform should be an easy task. You only have to follow a simple interface to do it, but knowing some advanced features and provided utilities may help to do it quicker...

class ITransform(Interface):
    """A transformation plugin -- tranform data somehow must be threadsafe and stateless"""

    inputs = Attribute("""list of imimetypes (or registered rfc-2046
                          names) this transform accepts as inputs""")

    output = Attribute("output imimetype as instance or rfc-2046 name"")

    def name(self):
        """return the name of the transform instance"""

    def convert(data, idata, **kwargs):
        """convert the data, store the result in idata and return that"""

class IDataStream(Interface):
    """data stream, is the result of a transform"""

    def setData(self, value):
        """set the main data produced by a transform, i.e. usually a string"""

    def getData():
        """provide access to the transformed data object, i.e. usually a string.
        This data may references subobjects.
        """

    def setSubObjects(self, objects):
        """set a dict-like object containing subobjects.
        keys should be object's identifier (e.g. usually a filename) and
        values object's content.
        """

    def getSubObjects(self):
        """return a dict-like object with any optional subobjects associated
        with the data"""

    def getMetadata():
        """return a dict-like object with any optional metadata from
        the transform"""

A transform receive data as an encoded string. A priori, no assumption can be made about the used encoding. Data returned by a transform must use the same encoding as received data, unless the transform provides a output_encoding attribute indicating the output encoding (for instance this may be usefull for XSLT based transforms).

You can make your transformation configurable through the ZMI by setting a config dictionary on your transform instance or class. Keys are parameter's name and values parameter's value. Another dictionnary config_metadata describes each parameter. In this mapping, keys are also parameter's name but values are a tree-uple : (<parameter's type>, <parameter's label>, <parameter's description>).

Possible types for parameters are:

int:field is an integer
string:field is a string
list:field is a list
dict:field is a dictionnary

You can look at the command and xml transforms for an example of configurable transform.

A transformation may produce some sub-objects, for instance when you convert a PDF document to HTML. That's the purpose of the setObjects method of the idatastream interface.

Transform utilities may be found in the libtransforms subpackage. You'll find there the following modules :

commandtransform
provides a base class for external command based transforms.
retransform
provides a base class for regular expression based transforms.
html4zope
provides a docutils HTML writer for Zope.
utils
provides some utilities functions.

Every transform should have its test... And it's easy to write a test for your transform ! Imagine you have made a transform named "colabeer" which transforms cola into beer (I let you find MIME type for these content types ;). Basically, your test file should be:

from test_transforms import make_tests

tests =('Products.MyTransforms.colabeer', "drink.cola", "drink.beer", None, 0)

def test_suite():
    return TestSuite([makeSuite(test) for test in make_tests()])

if __name__=='__main__':
    main(defaultTest='test_suite')

In this example:

  • "Products.MyTransforms.colabeer" is the module defining your transform (you can also give directly the transform instance).
  • "drink.cola" is the name of the file containing data to give to your transform as input.
  • "drink.beer" is the file containing expected transform result (what the getData method of idatastream will return).
  • Additional arguments (None and 0) are respectivly an optional normalizing function to apply to both the transform result and the output file content, and the number of subobjects that the transform is expected to produce.

This example supposes your test is in the tests directory of PortalTransforms and your input and output files respectively in tests/input and tests/output.