[Zope3-dev] Mail delivery failed: returning message to sender
Mail Delivery System
Mailer-Daemon at python.org
Tue Feb 17 07:55:45 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 iris1.directnic.com [204.251.10.81]: 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 1At4kg-0007oR-Ub; Tue, 17 Feb 2004 07:55:02 -0500
From: zope3-dev at zope.org (srichter)
Reply-To: zope3-dev at zope.org
To: ;
Subject: [NewWebDAVNamespace] (new) first draft (generated from LaTeX file)
Message-ID: <20040217075503EST 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/NewWebDAVNamespace/subscribeform>
List-Unsubscribe: <http://dev.zope.org/Wikis/DevSite/Projects/ComponentArchitecture/NewWebDAVNamespace/subscribeform>
List-Archive: <http://dev.zope.org/Wikis/DevSite/Projects/ComponentArchitecture/NewWebDAVNamespace>
List-Help: <http://dev.zope.org/Wikis/DevSite/Projects/ComponentArchitecture>
Date: Tue, 17 Feb 2004 07:55:03 -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/NewWebDAVNamespace
More information about the Zope3-dev
mailing list