[Zope3-dev] Mail delivery failed: returning message to sender

Mail Delivery System Mailer-Daemon at python.org
Tue Feb 17 07:57:49 EST 2004


This message was created automatically by mail delivery software.

A message that you sent could not be delivered to one or more of its
recipients. This is a permanent error. The following address(es) failed:

  corey at streamreel.net
    SMTP error from remote mailer after RCPT TO:<corey at streamreel.net>:
    host iris2.directnic.com [204.251.10.82]: 550 5.7.1 No such recipient

------ This is a copy of the message, including all the headers. ------

Return-path: <zope3-dev at zope.org>
Received: from cache1.zope.org ([12.155.117.38])
	by mail.python.org with esmtp (Exim 4.22)
	id 1At4mC-0008LH-KC; Tue, 17 Feb 2004 07:56:36 -0500
From: zope3-dev at zope.org (srichter)
Reply-To: zope3-dev at zope.org
To: ;
Subject: [UsingTALES] (new) first draft (generated from LaTeX file)
Message-ID: <20040217075636EST at dev.zope.org>
X-BeenThere: zope3-dev at zope.org
X-Zwiki-Version: 0.21.1
Precedence: bulk
List-Id:  <zope3-dev at zope.org>
List-Post: <mailto:zope3-dev at zope.org>
List-Subscribe: <http://dev.zope.org/Wikis/DevSite/Projects/ComponentArchitecture/UsingTALES/subscribeform>
List-Unsubscribe: <http://dev.zope.org/Wikis/DevSite/Projects/ComponentArchitecture/UsingTALES/subscribeform>
List-Archive: <http://dev.zope.org/Wikis/DevSite/Projects/ComponentArchitecture/UsingTALES>
List-Help: <http://dev.zope.org/Wikis/DevSite/Projects/ComponentArchitecture>
Date: Tue, 17 Feb 2004 07:56:36 -0500
X-Spam-Status: OK (default 0.000)

Registering new WebDAV Namespaces

  Status

    IsDraft

  Authors

    StephanRichter

  Difficulty

    Sprinter Level  

  Skills

    - You should know Zope 3's component architecture.

    - Be familiar with the WebDAV standard as proposed in RFC 2518.  


  Problem/Task

    WebDAV, as an advanced application of HTTP and XML, supports an
    unlimited amount metadata to be associated with any resource. This, of
    course, is non-sense for Zope related applications and could potentially
    make Zope vulnerable to DoS attacks, since someone could try to add huge
    amounts of meta-data to a resource. A namespace registry was created that
    manages the list of all available namespaces per content type
    (interface). This recipe will show you how to enable a new namespace
    called 'photo' for an 'IImage' object.  


  Recipe

    
  Introduction


    As mentioned above, WebDAV's unlimited support for XML namespaces
    make WebDAV very powerful but also provide an easy target for malicous
    attacks if not properly controlled. Therefore we would like to control,
    an object's WebDAV namespaces as well as the permissions required to
    access and modify the the namespace's attributes. Furthermore, there was
    a desire to integrate the namespace data and handling into Zope 3 as much
    as possible, so that other components could easily reuse the information.

    First of all we noticed that namespaces with attributes are really
    just schemas, so that we were able to describe a namespace using the Zope
    3 'schema' package. Now we were even able to write WebDAV widgets
    for the schema field types. Adapters could be used to connect a namespace
    to a content type or any other object. Finally, we needed a way to tell
    WebDAV which schema would be available under WebDAV and what namespace
    name it would be associated with. This was solved by a simple registry,
    which mapped a schema to a namespace name and vice versa. If an adapter
    from a given object/resource to a registered schema is available, then
    the object is considered to be able to provide the associated namespace.

    If one wants to provide a new namespace for a given object, the main
    task for the developer consists of creating a schema for the namespace
    and to provide an adapter from the object to the schema. The goal of this
    recipe will be to provide some additional metadata information about
    images that have been taken by digital cameras - images that are photos.

    Let's start a new product. Create a new package called 'photodavns' 
    in '<ZOPE3>/src/zope' .

    
  Creating the Namespace Schema


    The schema of the photo should contain some information that are
    usually provided by the camera. To implement the schema, open a new file
    'interfaces.py' and add the following code.::

      01 from zope.interface import Interface
      02 from zope.schema import Text, TextLine, Int, Float
      04 photodavns = "http://namespaces.zope.org/dav/photo/1.0"
      06 class IPhoto(Interface):
      07     """A WebDAV namespace to store photo-related meta data.
      09     The 'IPhoto' schema/namespace can be used in WebDAV clients to determine
      10     information about a particular picture. Obviously, this namespace makes
      11     only sense on Image objects.
      12     """
      14     height = Int(
      15         title=u"Height",
      16         description=u"Specifies the height in pixels.",
      17         min=1)
      19     width = Int(
      20         title=u"Width",
      21         description=u"Specifies the width in pixels.",
      22         min=1)
      24     equivalent35mm = TextLine(
      25         title=u"35mm equivalent",
      26         description=u"The photo's size in 35mm is equivalent to this amount")
      28     aperture = TextLine(
      29         title=u"Aperture",
      30         description=u"Size of the aperture.")
      32     exposureTime = Float(
      33         title=u"Exposure Time",
      34         description=u"Specifies the exposure time in seconds.")

    o Line 4: The name of the namespace is also part of the interface,
      so declare it here. The name must be a valid URL, otherwise the
      configuration directive that registers the namespace will fail.  


    There is nothing more of interest in this code; at this time you
    should be very comfortable with interfaces and schemas. If not, please
    read the recipes on interfaces and schemas.

    
  Implementing the  'IPhoto' to  'IImage' Adapter


    Next we need to implement the adapter, which will use annotations to
    store the attribute data. That means that the 'IImage' object must
    also implement 'IAttributeAnnotable' . With the knowledge of the
    annotations recipe, the following implementation should seem simple.::

      01 from zope.interface import implements
      02 from persistence.dict import PersistentDict
      03 from zope.app import zapi
      04 from zope.app.interfaces.annotation import IAnnotations
      05 from zope.app.interfaces.content.image import IImage
      06 from zope.schema import getFieldNames
      07 from interfaces import IPhoto, photodavns
      09 class ImagePhotoNamespace(object):
      10     """Implement IPhoto namespace for IImage."""
      12     implements(IPhoto)
      13     __used_for__ = IImage
      15     def __init__(self, context):
      16         self.context = context
      17         self._annotations = zapi.getAdapter(context, IAnnotations)
      18         if not self._annotations.get(photodavns):
      19             self._annotations[photodavns] = PersistentDict()
      21     def __getattr__(self, name):
      22         if not name in getFieldNames(IPhoto):
      23             return super(ImagePhotoNamespace, self).__getattribute__(name)
      24         return self._annotations[photodavns].get(name, None)
      26     def __setattr__(self, name, value):
      27         if not name in getFieldNames(IPhoto):
      28             return super(ImagePhotoNamespace, self).__setattr__(name, value)
      29         field = IPhoto[name]
      30         field.validate(value)
      31         self._annotations[photodavns][name] = value

    o Line 15-19: During initialization, get the annotations for the 'IImage' 
      object and create a dictionary where all the attribute values will be
      stored. Make sure that the dictionary is a 'PersistentDict' 
      instance, since otherwise the data will not be stored permanently in
      the ZODB.

    o Line 21-24: If the name of the requested attribute corresponds
      to a field in 'IPhoto' then we get the value from the
      annotations otherwise use the usual way. Note that I had to use '_' 
      for the normal case, since 'object' does not provide '_' 
      publically.

    o Line 26-31: We want to set attributes differently, if they are
      fields in the 'IPhoto' schema. If the name is a field, then the
      first task is to get the field which is then used to validate the
      value. This way we can enforce all specifications provided for the
      fields in the schema. If the validation passes, then store the value in
      the annotations.  


    
  Unit-Testing and Configuration


    For the unit tests of the adapter, we use doc tests. So we extend
    the adapter's class doc string to become::

      01 """Implement IPhoto namespace for IImage.
      03 Examples::
      05   >>> from zope.app.content.image import Image
      06   >>> image = Image()
      07   >>> photo = zapi.getAdapter(image, IPhoto)
      09   >>> photo.height is None
      10   True
      11   >>> photo.height = 768
      12   >>> photo.height
      13   768
      14   >>> photo.height = u'100'
      15   Traceback (most recent call last):
      16   ...
      17   ValidationError: (u'Wrong type', u'100', (<type 'int'>, <type 'long'>))
      19   >>> photo.width is None
      20   True
      21   >>> photo.width = 1024
      22   >>> photo.width
      23   1024
      25   >>> photo.equivalent35mm is None
      26   True
      27   >>> photo.equivalent35mm = u'41 mm'
      28   >>> photo.equivalent35mm
      29   u'41 mm'
      31   >>> photo.aperture is None
      32   True
      33   >>> photo.aperture = u'f/2.8'
      34   >>> photo.aperture
      35   u'f/2.8'
      37   >>> photo.exposureTime is None
      38   True
      39   >>> photo.exposureTime = 0.031
      40   >>> photo.exposureTime
      41   0.031
      43   >>> photo.occasion
      44   Traceback (most recent call last):
      45   ...
      46   AttributeError: 'ImagePhotoNamespace' object has no attribute 'occasion'
      47 """

    You can see that the example code covers pretty much every possible
    situation.  

    o Line 5-7: Use the standard 'Image' content component as context
      for the adapter. Then we use the component architecture to get the
      adapter. This already tests whether the constructor - which is not
      trivial in this case - does not cause an exception.

    o Line 14-17: Test that the validation of the field's values works
      correctly.

    o Line 43-46: We also need to make sure that no non-existing
      attributes can be assigned a value.  


    To make the tests runnable, add a file named 'tests.py' and add the
    following test code.::

      01 import unittest
      02 from zope.app.tests import ztapi
      03 from zope.app.tests.placelesssetup import setUp, tearDown 
      04 from zope.app.attributeannotations import AttributeAnnotations
      05 from zope.app.content.image import Image
      06 from zope.app.interfaces.annotation import IAnnotations
      07 from zope.app.interfaces.annotation import IAttributeAnnotatable
      08 from zope.app.interfaces.content.image import IImage
      09 from zope.interface import classImplements
      10 from zope.products.photodavns.interfaces import IPhoto
      11 from zope.products.photodavns import ImagePhotoNamespace
      12 from zope.testing.doctestunit import DocTestSuite
      14 def customSetUp():
      15     setUp()
      16     ztapi.provideAdapter(IImage, IPhoto, ImagePhotoNamespace)
      17     ztapi.provideAdapter(IAttributeAnnotatable, IAnnotations,
      18                          AttributeAnnotations)
      19     classImplements(Image, IAttributeAnnotatable)
      21 def test_suite():
      22     return unittest.TestSuite((
      23         DocTestSuite('zope.products.photodavns', customSetUp, tearDown),
      24         ))
      26 if __name__ == '__main__':
      27     unittest.main(defaultTest='test_suite')

    o Line 14-19: We need to setup some additional adapters to make
      the tests work. First, of course, we need to register our adapter. Then
      we also need to provide the 'AttributeAdapter' , so that the 'ImagePhotoNamespace' 
      will find the annotations for the image. Finally, since 'Image' 
      does not implement 'IAttributeAnnotable' directly (it is usually
      done in a ZCML directive), we need to declare it manually here.

    o 21-24: The 'setUp()' and 'tearDown()' methods for a doc test can
      be passed as arguments to the 'DocTestSuite' constructor.  


    From the Zope 3 root directory, you can now execute the tests using::

      python test.py -vp zope.products.photodavns
        
  Functional Testing


    Now let's see our new namespace in action. Unfortunately, I am not
    aware of any WebDAV tools that can handle genericly any namespace. For
    this reason we will use functional tests for confirming the correct
    behavior.

    In the first step we will test whether 'PROPFIND' will (a)
    understand the namespace and (b) return the right values from the
    annotation of the image. Here is the complete code for the functional
    test, which you should place in a file called 'ftests.py' .::

      01 import unittest
      02 from transaction import get_transaction
      03 from xml.dom.minidom import parseString as parseXML
      04 from zope.app import zapi
      05 from zope.app.content.image import Image
      06 from zope.app.dav.ftests.dav import DAVTestCase
      07 from zope.component import getAdapter
      08 from zope.products.photodavns.interfaces import IPhoto
      09 from zope.products.photodavns import ImagePhotoNamespace
      11 property_request = '''\
      12 <?xml version="1.0" encoding="utf-8" ?>
      13 <propfind xmlns="DAV:">
      14   <prop xmlns:photo="http://namespaces.zope.org/dav/photo/1.0">
      15     <photo:height />
      16     <photo:width />
      17     <photo:equivalent35mm />
      18     <photo:aperture />
      19     <photo:exposureTime />
      20   </prop>
      21 </propfind>
      22 '''
      24 data = {'height': 768, 'width': 1024, 'equivalent35mm': u'41 mm',
      25         'aperture': u'f/2.8', 'exposureTime': 0.031}
      27 class IPhotoNamespaceTests(DAVTestCase):
      29     def createImage(self):
      30         img = Image()
      31         photo = ImagePhotoNamespace(img)
      32         for name, value in data.items():
      33             setattr(photo, name, value)
      34         root = self.getRootFolder()
      35         root['img.jpg'] = img
      36         get_transaction().commit()
      38     def test_propfind_fields(self):
      39         self.createImage()
      40         response = self.publish(
      41             '/img.jpg/',
      42             env={'REQUEST_METHOD':'PROPFIND',
      43                  'HTTP_Content_Type': 'text/xml'},
      44             request_body=property_request)
      45         self.assertEqual(response.getStatus(), 207)
      46         xml = parseXML(response.getBody())
      47         node = xml.documentElement.getElementsByTagName('prop')[0]
      49         for name, value in data.items():
      50             attr_node = node.getElementsByTagName(name)[0]
      51             self.assertEqual(attr_node.firstChild.data, unicode(value))
      53 def test_suite():
      54     return unittest.TestSuite((
      55         unittest.makeSuite(IPhotoNamespaceTests),
      56         ))
      58 if __name__ == '__main__':
      59     unittest.main(defaultTest='test_suite')

    o Line 11-22: This is the XML request that will be sent to the
      Zope 3 WebDAV server. Note that we need to make sure that The first
      line starts at the beginning of the string, since otherwise the XML
      parser causes a failure. In the string, we simply request explicitely
      all attributes of the 'photo' namespace.

    o Line 24-25: Here is the data that is being setup in the
      annotation and that we expect to receive from the 'PROPFIND' 
      request.

    o Line 29-36: This helper method creates an image and sets the
      photo data on the image, so that we can access it. Note that we have to
      commit a transaction at this point, otherwise the image will not be
      found in the ZODB.

    o 38-51: First we create the image so that it will be available.
      Then we just publish our request with a carefully constructed
      environment. To make the request a 'PROPFIND' call, you need to
      create a environment variable named 'REQUEST' . Since we send
      XML as the body of the request, we need to set the content type to
      "text/xml", which is done with a 'HTTP' environment entry.

      The answer we receive from the server should be 207, which
      signalizes that the 'PROPFIND' call was successful and the data
      is enclosed in the body. We then parse the XML body simply using
      Python's built-in 'xml.dom.minidom' package. The rest of the
      test code simply uses DOM to ensure that all of the requested
      attributes were returned and the data is correct.  


    Once you are done with the functional test, you can run it using the
    usual method::

      python test.py -vpf zope.products.photodavns
        The '-f' option executes only functional tests. Functional tests
    are recognized by their module name, which must be 'ftests' in
    comparison to 'tests' for regular unit tests.  


  Exercises

    - Exercise 1: Implement 'height' and 'width' in a way that it uses
      the 'IImage' 's 'getImageSize()' method to get the values.

    - Exercise 2: JPEG files support EXIF metadata tags that often
      already contain the data provided by the 'IPhoto' interface, so
      change the adapter in a way that it first tries to retrieve the data
      from the image before using annotation data. See 'http://topo.math.u-psud.fr/' 
      for a Python implementation of a EXIF tag retriever.  

--
forwarded from http://dev.zope.org/Wikis/DevSite/Projects/ComponentArchitecture/UsingTALES



More information about the Zope3-dev mailing list