############################################################################## # # Copyright (c) 2004 Philipp "philiKON" von Weitershausen # # This software is distributed under the terms of the Zope Public # License (ZPL) v2.1. # ############################################################################## """Core machinery The components in here allow Pentecost to be as transparent as it is. It works with any IContainer and any content type. $Id$ """ from zope.interface import implements, directlyProvides from zope.interface.interface import InterfaceClass from zope.security.proxy import trustedRemoveSecurityProxy from zope.app import zapi from zope.app.container.interfaces import IContainer from zope.app.container.constraints import ItemTypePrecondition from zope.app.content.interfaces import IContentType from zope.app.component.interface import provideInterface from interfaces import IPentecostAdd, IPentecostContainer class PentecostInterface(InterfaceClass): """Pickable interface for pentecost objects Pentecost creates interfaces on-the-fly, thus the pickling machinery cannot rely on finding it there again and wants to pickle it. However, interfaces are not pickable (yet), so we do some tricks to recreate on-the-fly interfaces upon demand. Plus, we can hide some functionality here. For example PentecostInterfaces always extend ``IPentecostContainer``: >>> from zope.interface import Interface >>> IFoo = PentecostInterface('IFoo', Interface) >>> IFoo.extends(IPentecostContainer) True By implementing a method of the pickling protocol, they avoid pickling: >>> reduced = IFoo.__reduce__()[1] >>> reduced[0] 'IFoo' >>> reduced[1] is Interface True Finally, to play nice, they are hasheable: >>> hash(IFoo) == hash('IFoo') True XXX test precondition on __setitem__ """ def __init__(self, name, content_type): self.content_type = content_type class Base(IPentecostContainer): def __setitem__(name, object): """Add an item""" __setitem__.precondition = ItemTypePrecondition(content_type) super(PentecostInterface, self).__init__(name, (Base,)) def __reduce__(self): """Tell the pickling machiner not to pickle a PentecostInterface but to recreate instances with whatever we return here""" # That we return content_type here implies that it needs to be # located in a module like a regular interface, a risk that # we're willing to take for now return (PentecostInterface, (self.getName(), self.content_type)) def __hash__(self): return hash(self.__name__) class ContainerAddingAdapter(object): """Adapter for young, innocent IContainers that are to be transvestited to pentecost objects. First, we need a content type that we want our pentecost object to contain and an object implementing it: >>> from zope.interface import implements, providedBy, Interface >>> class ISampleType(Interface): ... pass >>> class SampleObject(object): ... implements(ISampleType) >>> sample = SampleObject() Let's create a container then. This poor bastard doesn't know yet what's gonna hit him... Muahaha! >>> from zope.app.container.sample import SampleContainer >>> container = SampleContainer() See, this container does not have a 2nd bottom or anything. It's a regular container: >>> IPentecostContainer.providedBy(container) False >>> from zope.app.container.constraints import checkObject >>> checkObject(container, "some_obj", object()) As you can see, no constraint on it yet. Now to begin the delicate operation... We adapt the container and set the content type. >>> adapter = ContainerAddingAdapter(container) >>> adapter.content_type = ISampleType It should now have a constraint that only allows us to add ISampleTypes to it: >>> checkObject(container, "sample", sample) the simple object from before fails to be added: >>> from zope.app.container.interfaces import InvalidItemType >>> try: ... checkObject(container, "some_obj", object()) ... except InvalidItemType: ... print 'Failed' ... else: ... print 'Should have failed' Failed """ implements(IPentecostAdd) __used_for__ = IContainer def __init__(self, context): self.context = context def getContentType(self): return self.context.content_type def setContentType(self, content_type): if IPentecostContainer.providedBy(self.context): # XXX shall we raise an error here? return self.context.content_type = content_type # create a custom interface based on the content type for the # pentecost object and make it a content type # TODO: maybe do something about unique names name = "Pentecost" iface = PentecostInterface(name, content_type) directlyProvides(iface, IContentType) # directlyProvides freaks out about a security proxy context = trustedRemoveSecurityProxy(self.context) directlyProvides(context, iface) content_type = property(getContentType, setContentType)