[Checkins] SVN: zope.publisher/trunk/ - Move skin related code from zope.publisher.interfaces.browser and

Roger Ineichen roger at projekt01.ch
Sun Mar 8 17:27:26 EDT 2009


Log message for revision 97670:
  - Move skin related code from zope.publisher.interfaces.browser and
    zope.publisher.browser to zope.publihser.interfaces and
    zope.publisher.skinnable and provide BBB imports.
  - added tests for the skin concept
  See skinnable.txt for more information.

Changed:
  U   zope.publisher/trunk/CHANGES.txt
  U   zope.publisher/trunk/setup.py
  U   zope.publisher/trunk/src/zope/publisher/browser.py
  U   zope.publisher/trunk/src/zope/publisher/configure.zcml
  U   zope.publisher/trunk/src/zope/publisher/http.py
  U   zope.publisher/trunk/src/zope/publisher/interfaces/__init__.py
  U   zope.publisher/trunk/src/zope/publisher/interfaces/browser.py
  A   zope.publisher/trunk/src/zope/publisher/skinnable.py
  A   zope.publisher/trunk/src/zope/publisher/skinnable.txt
  U   zope.publisher/trunk/src/zope/publisher/tests/test_baserequest.py
  U   zope.publisher/trunk/src/zope/publisher/tests/test_browserrequest.py
  A   zope.publisher/trunk/src/zope/publisher/tests/test_skinnable.py

-=-
Modified: zope.publisher/trunk/CHANGES.txt
===================================================================
--- zope.publisher/trunk/CHANGES.txt	2009-03-08 20:54:11 UTC (rev 97669)
+++ zope.publisher/trunk/CHANGES.txt	2009-03-08 21:27:26 UTC (rev 97670)
@@ -1,9 +1,14 @@
 CHANGES
 =======
 
-3.5.7dev (unreleased)
----------------------
+3.6.0 (unreleased)
+------------------
 
+- Clean-up: Move skin related code from zope.publisher.interfaces.browser and
+  zope.publisher.browser to zope.publihser.interfaces and
+  zope.publisher.skinnable and provide BBB imports. See skinnable.txt for more
+  information.
+
 - Fix: ensure that we only apply skin interface in setDefaultSkin which also
   provide IBrowserSkinType. This will ensure that we find a skin if the
   applySkin method will lookup for a skin based on this type interface.

Modified: zope.publisher/trunk/setup.py
===================================================================
--- zope.publisher/trunk/setup.py	2009-03-08 20:54:11 UTC (rev 97669)
+++ zope.publisher/trunk/setup.py	2009-03-08 21:27:26 UTC (rev 97670)
@@ -24,7 +24,7 @@
 """
 
 setup(name='zope.publisher',
-      version='3.5.7dev',
+      version='3.6.0dev',
       url='http://pypi.python.org/pypi/zope.publisher',
       license='ZPL 2.1',
       author='Zope Corporation and Contributors',

Modified: zope.publisher/trunk/src/zope/publisher/browser.py
===================================================================
--- zope.publisher/trunk/src/zope/publisher/browser.py	2009-03-08 20:54:11 UTC (rev 97669)
+++ zope.publisher/trunk/src/zope/publisher/browser.py	2009-03-08 21:27:26 UTC (rev 97670)
@@ -37,18 +37,24 @@
 from zope.location import Location
 
 from zope.publisher.interfaces import NotFound
+from zope.publisher.interfaces import IDefaultSkin
 from zope.publisher.interfaces.browser import IBrowserRequest
 from zope.publisher.interfaces.browser import IDefaultBrowserLayer
-from zope.publisher.interfaces.browser import IDefaultSkin
 from zope.publisher.interfaces.browser import IBrowserApplicationRequest
 from zope.publisher.interfaces.browser import IBrowserView
 from zope.publisher.interfaces.browser import IBrowserPage
-from zope.publisher.interfaces.browser import ISkinType
 from zope.publisher.interfaces.browser import IBrowserSkinType
-from zope.publisher.interfaces.browser import ISkinChangedEvent
 from zope.publisher.interfaces.http import IHTTPRequest
 from zope.publisher.http import HTTPRequest, HTTPResponse
 
+# BBB imports, this compoennts get moved from this module
+from zope.publisher.interfaces import ISkinType #BBB import
+from zope.publisher.interfaces import ISkinChangedEvent #BBB import
+from zope.publisher.skinnable import setDefaultSkin #BBB import
+from zope.publisher.skinnable import applySkin #BBB import
+from zope.publisher.skinnable import SkinChangedEvent #BBB import
+
+
 __ArrayTypes = (ListType, TupleType)
 
 start_of_header_search=re.compile('(<head[^>]*>)', re.I).search
@@ -914,197 +920,3 @@
 def getDefaultSkin(request):
     """Returns the IDefaultSkin layer for IBrowserRequest."""
     return IDefaultBrowserLayer
-
-
-def setDefaultSkin(request):
-    """Sets the default skin for the request.
-
-    The default skin is a marker interface that can be registered as an
-    adapter that provides IDefaultSkin for the request type. A default skin
-    interface like any other skin must also provide IBrowserSkinType. This is
-    important since applySkin will lookup for skins based on this type.
-
-    Note: Any interfaces that are directly provided by the request coming into
-    this method are replaced by the applied layer/skin interface. This is very
-    important since the retry pattern can use a clean request without any
-    directly provided interface.
-
-    If a default skin is not available, the fallback default skin get applied
-    if available for the given request type. The default fallback skin is
-    implemented as an named adapter factory providing IDefaultSkin and
-    using ``default`` as name. 
-    
-    Important to know is that some skin adapters get registered as interfaces
-    and the fallback skins as adapters. See the defaultSkin directive in 
-    zope.app.publication.zcml for more information which registers plain
-    interfaces as adapters which are not adaptable. (issue?)
-
-    Each request can only have one (unnamed) default skin and will fallback to
-    the named (default) fallback skin if available.
-
-    Only the IBrowserRequest provides such a default fallback adapter. This
-    adapter will apply the IDefaultBrowserLayer if no explicit default skin
-    is registered.
-
-    To illustrate, we'll first use setDefaultSkin without a registered
-    IDefaultSkin adapter:
-
-      >>> class Request(object):
-      ...     implements(IBrowserRequest)
-
-      >>> request = Request()
-      >>> IDefaultBrowserLayer.providedBy(request)
-      False
-
-    If we try to set a default skin and no one exist we will not fail but
-    nothing happens
-
-      >>> setDefaultSkin(request)
-
-    Make sure our IDefaultBrowserLayer provides the IBrowserSkinType interface.
-    This is done in the configure.zcml using the interface directive:
-    
-      >>> IBrowserSkinType.providedBy(IDefaultBrowserLayer)
-      False
-
-      >>> alsoProvides(IDefaultBrowserLayer, IBrowserSkinType)
-      >>> IBrowserSkinType.providedBy(IDefaultBrowserLayer)
-      True
-
-    The getDefaultSkin provides an adapter providing IDefaultSkin which we
-    register as named adapter using ``default`` as name:
-    
-      >>> zope.component.provideAdapter(getDefaultSkin,
-      ...     (IBrowserRequest,), IDefaultSkin, name='default')
-
-      >>> setDefaultSkin(request)
-      >>> IDefaultBrowserLayer.providedBy(request)
-      True
-
-    When we register a default layer, wihtout that the skin provides an
-    ISkinType the skin doesn't get applied:
-
-      >>> from zope.interface import Interface
-      >>> class IMySkin(Interface):
-      ...     pass
-      >>> zope.component.provideAdapter(IMySkin, (IBrowserRequest,),
-      ...                               IDefaultSkin)
-
-      >>> setDefaultSkin(request)
-      >>> IMySkin.providedBy(request)
-      False
-      >>> IDefaultBrowserLayer.providedBy(request)
-      True
-
-    The default skin must provide IBrowserSkinType:
-    
-      >>> alsoProvides(IMySkin, IBrowserSkinType)
-      >>> IBrowserSkinType.providedBy(IMySkin)
-      True
-
-    setDefaultSkin uses the layer instead of IDefaultBrowserLayer:
-
-      >>> request = Request()
-      >>> IMySkin.providedBy(request)
-      False
-      >>> IDefaultBrowserLayer.providedBy(request)
-      False
-
-      >>> setDefaultSkin(request)
-
-      >>> IMySkin.providedBy(request)
-      True
-      >>> IDefaultBrowserLayer.providedBy(request)
-      False
-
-    Any interfaces that are directly provided by the request coming into this
-    method are replaced by the applied layer/skin interface. This is important
-    for our retry pattern which will ensure that we start with a clean request:
-
-      >>> request = Request()
-      >>> class IFoo(Interface):
-      ...     pass
-      >>> directlyProvides(request, IFoo)
-      >>> IFoo.providedBy(request)
-      True
-      >>> setDefaultSkin(request)
-      >>> IFoo.providedBy(request)
-      False
-
-    """
-    adapters = zope.component.getSiteManager().adapters
-    skin = adapters.lookup((providedBy(request),), IDefaultSkin, '')
-    if skin is None:
-        # find a named ``default`` adapter providing IDefaultSkin as fallback
-        skin = adapters.lookup((providedBy(request),), IDefaultSkin,
-            'default')
-    if skin is not None:
-        try:
-            # the default fallback skin is registered as a named adapter
-            skin = skin(request)
-        except TypeError, e:
-            # the defaultSkin directive registers skins as interfaces and not
-            # as adapters (issue?)
-            pass
-        if ISkinType.providedBy(skin):
-            # silently ignore skins which do not provide ISkinType
-            directlyProvides(request, skin)
-
-
-def applySkin(request, skin, skinType=IBrowserSkinType):
-    """Change the presentation skin for this request.
-
-    >>> import pprint
-    >>> from zope.interface import Interface
-    >>> class SkinA(Interface): pass
-    >>> directlyProvides(SkinA, IBrowserSkinType)
-    >>> class SkinB(Interface): pass
-    >>> directlyProvides(SkinB, IBrowserSkinType)
-    >>> class IRequest(Interface): pass
-
-    >>> class Request(object):
-    ...     implements(IRequest)
-
-    >>> req = Request()
-
-    >>> applySkin(req, SkinA)
-    >>> pprint.pprint(list(providedBy(req).interfaces()))
-    [<InterfaceClass zope.publisher.browser.SkinA>,
-     <InterfaceClass zope.publisher.browser.IRequest>]
-
-    >>> applySkin(req, SkinB)
-    >>> pprint.pprint(list(providedBy(req).interfaces()))
-    [<InterfaceClass zope.publisher.browser.SkinB>,
-     <InterfaceClass zope.publisher.browser.IRequest>]
-
-    Changing the skin on a request triggers the ISkinChanged event:
-
-    >>> import zope.component
-    >>> from zope.publisher.interfaces.browser import ISkinChangedEvent
-    >>> def receiveSkinEvent(event):
-    ...     print event.request
-    >>> zope.component.provideHandler(receiveSkinEvent, (ISkinChangedEvent,))
-    >>> applySkin(req, SkinA)   # doctest: +ELLIPSIS
-    <zope.publisher.browser.Request object at 0x...>
-
-    Make sure our registrations go away again.
-
-    >>> from zope.testing.cleanup import cleanUp
-    >>> cleanUp()
-
-    """
-    # Remove all existing skin declarations (commonly the default skin) based
-    # on the given skin type.
-    ifaces = [iface for iface in directlyProvidedBy(request)
-              if not skinType.providedBy(iface)]
-    # Add the new skin.
-    ifaces.append(skin)
-    directlyProvides(request, *ifaces)
-    zope.event.notify(SkinChangedEvent(request))
-
-class SkinChangedEvent(object):
-
-    zope.interface.implements(ISkinChangedEvent)
-
-    def __init__(self, request):
-        self.request = request

Modified: zope.publisher/trunk/src/zope/publisher/configure.zcml
===================================================================
--- zope.publisher/trunk/src/zope/publisher/configure.zcml	2009-03-08 20:54:11 UTC (rev 97669)
+++ zope.publisher/trunk/src/zope/publisher/configure.zcml	2009-03-08 21:27:26 UTC (rev 97670)
@@ -28,7 +28,7 @@
       name="default"
       factory=".browser.getDefaultSkin"
       for="zope.publisher.interfaces.browser.IBrowserRequest"
-      provides="zope.publisher.interfaces.browser.IDefaultSkin"
+      provides="zope.publisher.interfaces.IDefaultSkin"
       />
 
   <apidoc:bookchapter

Modified: zope.publisher/trunk/src/zope/publisher/http.py
===================================================================
--- zope.publisher/trunk/src/zope/publisher/http.py	2009-03-08 20:54:11 UTC (rev 97669)
+++ zope.publisher/trunk/src/zope/publisher/http.py	2009-03-08 21:27:26 UTC (rev 97670)
@@ -451,7 +451,7 @@
         # restore the default skin
         if IBrowserRequest.providedBy(self):
             # only browser requests have skins
-            zope.publisher.browser.setDefaultSkin(request)
+            zope.publisher.skinnable.setDefaultSkin(request)
 
         request.setPublication(self.publication)
         request._retry_count = self._retry_count

Modified: zope.publisher/trunk/src/zope/publisher/interfaces/__init__.py
===================================================================
--- zope.publisher/trunk/src/zope/publisher/interfaces/__init__.py	2009-03-08 20:54:11 UTC (rev 97669)
+++ zope.publisher/trunk/src/zope/publisher/interfaces/__init__.py	2009-03-08 21:27:26 UTC (rev 97670)
@@ -17,10 +17,12 @@
 """
 __docformat__ = "reStructuredText"
 from zope.interface import Interface, Attribute, implements
+from zope.interface.interfaces import IInterface
 from zope.interface.common.mapping import IEnumerableMapping
 from zope.interface.common.interfaces import IException, ILookupError
 from zope.security.interfaces import Unauthorized, IParticipation
 
+
 class IPublishingException(IException):
     pass
 
@@ -457,6 +459,33 @@
     """The basic request contract
     """
 
+
+class ISkinType(IInterface):
+    """Base interface for skin types."""
+
+
+class ISkinnable(Interface):
+    """A skinnable (request) can provide a skin.
+    
+    The implementation in BrowserRequest will apply a default skin/layer called
+    ``IDefaultBrowserLayer`` if not default skin get registered.
+    """
+
+
+class IDefaultSkin(Interface):
+    """Any component providing this interface must be a skin.
+
+    This is a marker interface, so that we can register the default skin as an
+    adapter from the presentation type to `IDefaultSkin`.
+    """
+
+
+class ISkinChangedEvent(Interface):
+    """Event that gets triggered when the skin of a request is changed."""
+
+    request = Attribute("The request for which the skin was changed.")
+
+
 class IView(Interface):
     """Generic view contract"""
 

Modified: zope.publisher/trunk/src/zope/publisher/interfaces/browser.py
===================================================================
--- zope.publisher/trunk/src/zope/publisher/interfaces/browser.py	2009-03-08 20:54:11 UTC (rev 97669)
+++ zope.publisher/trunk/src/zope/publisher/interfaces/browser.py	2009-03-08 21:27:26 UTC (rev 97670)
@@ -23,10 +23,18 @@
 
 from zope.publisher.interfaces import IPublication
 from zope.publisher.interfaces import IPublishTraverse
+from zope.publisher.interfaces import ISkinType
+from zope.publisher.interfaces import ISkinnable
 from zope.publisher.interfaces import IView
 from zope.publisher.interfaces.http import IHTTPApplicationRequest
 from zope.publisher.interfaces.http import IHTTPRequest
 
+# BBB moved to zope.publisher.interfaces since not only browser reuquest
+# can use the skin pattern
+from zope.publisher.interfaces import IDefaultSkin # BBB import
+from zope.publisher.interfaces import ISkinChangedEvent # BBB import
+
+
 class IBrowserApplicationRequest(IHTTPApplicationRequest):
     """Browser-specific requests
     """
@@ -74,28 +82,6 @@
         """
 
 
-class ISkinnable(Interface):
-    """A skinnable (request) can provide a skin.
-    
-    The implementation in BrowserRequest will apply a default skin/layer called
-    ``IDefaultBrowserLayer`` if not default skin get registered.
-    """
-
-
-class IDefaultSkin(Interface):
-    """Any component providing this interface must be a skin.
-
-    This is a marker interface, so that we can register the default skin as an
-    adapter from the presentation type to `IDefaultSkin`.
-    """
-
-
-class ISkinChangedEvent(Interface):
-    """Event that gets triggered when the skin of a request is changed."""
-
-    request = Attribute("The request for which the skin was changed.")
-
-
 class IBrowserRequest(IHTTPRequest, ISkinnable):
     """Browser-specific Request functionality.
 
@@ -142,8 +128,6 @@
 class IDefaultBrowserLayer(IBrowserRequest):
     """The default layer."""
 
-class ISkinType(IInterface):
-    """Base interface for skin types."""
 
 class IBrowserSkinType(ISkinType):
     """A skin is a set of layers."""

Added: zope.publisher/trunk/src/zope/publisher/skinnable.py
===================================================================
--- zope.publisher/trunk/src/zope/publisher/skinnable.py	                        (rev 0)
+++ zope.publisher/trunk/src/zope/publisher/skinnable.py	2009-03-08 21:27:26 UTC (rev 97670)
@@ -0,0 +1,74 @@
+##############################################################################
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Browser-specific Publisher classes
+
+Here we define the specific 'BrowserRequest' and 'BrowserResponse' class. The
+big improvement of the 'BrowserRequest' to 'HTTPRequest' is that is can handle
+HTML form data and convert them into a Python-native format. Even file data is
+packaged into a nice, Python-friendly 'FileUpload' object.
+
+$Id: browser.py 97505 2009-03-05 00:47:39Z rogerineichen $
+"""
+__docformat__ = 'restructuredtext'
+
+import zope.component
+import zope.interface
+
+from zope.publisher import interfaces
+
+
+class SkinChangedEvent(object):
+    """Skin changed event."""
+
+    zope.interface.implements(interfaces.ISkinChangedEvent)
+
+    def __init__(self, request):
+        self.request = request
+
+
+def setDefaultSkin(request):
+    """Sets the default skin for a given request."""
+    adapters = zope.component.getSiteManager().adapters
+    skin = adapters.lookup((zope.interface.providedBy(request),),
+        interfaces.IDefaultSkin, '')
+    if skin is None:
+        # find a named ``default`` adapter providing IDefaultSkin as fallback
+        skin = adapters.lookup((zope.interface.providedBy(request),),
+            interfaces.IDefaultSkin, 'default')
+    if skin is not None:
+        try:
+            # the default fallback skin is registered as a named adapter
+            skin = skin(request)
+        except TypeError, e:
+            # the defaultSkin directive registers skins as interfaces and not
+            # as adapters (issue?)
+            pass
+        if interfaces.ISkinType.providedBy(skin):
+            # silently ignore skins which do not provide ISkinType
+            zope.interface.directlyProvides(request, skin)
+        else:
+            raise TypeError("Skin interface %s doesn't provide ISkinType" % 
+                skin)
+
+
+def applySkin(request, skin):
+    """Change the presentation skin for this request."""
+    # Remove all existing skin type declarations (commonly the default skin)
+    # based on the given skin type.
+    ifaces = [iface for iface in zope.interface.directlyProvidedBy(request)
+              if not interfaces.ISkinType.providedBy(iface)]
+    # Add the new skin.
+    ifaces.append(skin)
+    zope.interface.directlyProvides(request, *ifaces)
+    zope.event.notify(SkinChangedEvent(request))

Added: zope.publisher/trunk/src/zope/publisher/skinnable.txt
===================================================================
--- zope.publisher/trunk/src/zope/publisher/skinnable.txt	                        (rev 0)
+++ zope.publisher/trunk/src/zope/publisher/skinnable.txt	2009-03-08 21:27:26 UTC (rev 97670)
@@ -0,0 +1,304 @@
+=========
+Skinnable
+=========
+
+Request can provide skins. But what's exactly a skin. At the code level, a skin
+is just an interface which a request provides. Why do we need skins? We can use
+skins for register different adapter.
+
+That's a little bit much use of the word skin. Let's explain it mor detailed.
+A skin is an interface which provides a type interface. This type interface 
+is called ISkinType. The zope.publisher right now provides only one specific
+skin type interface used in the IBrowserRequest implementation. This interface
+is called BrowserSkinType.
+
+Since the zope server provides request factories for biuld a request, each
+such request type could provide it's own skin type interface. This ensures that
+we can register a skin type for each request.
+
+Now a more high level point of view. A skin is a concept which we can use for
+provide different kind of views, templates or other adapter adapting a request.
+This skins are the key component for provide different kind of application
+layers. Then a skin makes it possible that an application can act very
+different with each skin. Of corse that's only the case at the interaction
+level where the request is involved. But that's moste the time the case since
+we have an web application server. 
+
+Another part of the skinnable concept is that a skin can define a default skin.
+This is done within the IDefaultSkin interface. Such a default skin get defined
+at the level request implementation level. Such a default skin can get overriden
+in a custom setup. Overriding a skin can be done by using the defaultSkin
+directive offeren from zope.app.publication.zcml.
+
+Why does a request need a default skin. If a request needs to provide some
+pluggable concepts which requires that a default adapter is registered for
+a request, this adapter could be registered for the default skin. If a project
+likes to use another pattern and needs to register another request adapter, the
+project could register it's own skin and register the custom adapter for this
+new project based skin. This is very handy and allows to skip a complete
+default skin based setup for a given request.
+
+In general this means a request interface and the request class wich implements
+the request interface does only provide the basic API but no adapters if the
+request needs to delegate things to an adapter. For such a request a default
+skin can get defined. This default skin can provide all adatpers which the
+request implementation needs to have. This gives us to option to replace the
+default skin within an own skin and provide custom adapters.
+
+Our exmple will define a full request and all it's component from scratch.
+it doesn't depend on IBrowserRequest. We'll use a JSON-RPC as sample like
+the z3c.jsonrpc package provides.
+
+Layers and Skins
+----------------
+
+We also use the term layer if we talk about skins. A layer or skin layer is an
+interface registered as a ISkinType without a name. Zope provides a traversal
+pattern which allows to traverse a skin within a skin namespace called
+``skin``. This allows to traverse to traverse to a method called applySkin
+which will aplly a registered named skin. this means if we register a ISkinType
+as within an optional name argument, we will register a skin. if we register a
+ISkinType without a name just we register a layer. This means, layers are not
+traversable ISkinType interfaces.
+
+Let's start define a request:
+
+  >>> from zope.publisher.interfaces import IRequest
+  >>> class IJSONRequest(IRequest):
+  ...     """JSON request."""
+
+And we define a skin type:
+
+  >>> from zope.publisher.interfaces import ISkinType
+  >>> class IJSONSkinType(ISkinType):
+  ...     """JSON skin type."""
+
+A request would implement the IJSONRequest interface but not the request type
+interface:
+
+  >>> import zope.interface
+  >>> from zope.publisher.base import BaseRequest
+  >>> class JSONRequest(BaseRequest):
+  ...     """JSON request implementation."""
+  ...     zope.interface.implements(IJSONRequest)
+
+Now our request provides IJSONRequest because it implement that interface:
+
+  >>> from StringIO import StringIO
+  >>> request = JSONRequest(StringIO(''), {})
+  >>> IJSONRequest.providedBy(request)
+  True
+
+
+setDefaultSkin
+--------------
+
+The default skin is a marker interface that can be registered as an
+adapter that provides IDefaultSkin for the request type. A default skin
+interface like any other skin must also provide ISkinType. This is
+important since applySkin will lookup for skins based on this type.
+
+Note: Any interfaces that are directly provided by the request coming into
+this method are replaced by the applied layer/skin interface. This is very
+important since the retry pattern can use a clean request without any
+directly provided interface after a retry get started.
+
+If a default skin is not available, the fallback default skin get applied
+if available for the given request type. The default fallback skin is
+implemented as an named adapter factory providing IDefaultSkin and
+using ``default`` as name. 
+
+Important to know is that some skin adapters get registered as interfaces
+and the fallback skins as adapters. See the defaultSkin directive in 
+zope.app.publication.zcml for more information which registers plain
+interfaces as adapters which are not adaptable. (issue?)
+
+Each request can only have one (unnamed) default skin and will fallback to
+the named (default) fallback skin if available.
+
+Only the IBrowserRequest provides such a default fallback adapter. This
+adapter will apply the IDefaultBrowserLayer if no explicit default skin
+is registered for IBrowserRequest.
+
+Our test setup requires a custom default layer which we will apply to our
+request. Let's define a custm layer:
+
+  >>> class IJSONDefaultLayer(zope.interface.Interface):
+  ...     """JSON default layyer."""
+
+To illustrate, we'll first use setDefaultSkin without a registered
+IDefaultSkin adapter:
+
+  >>> IJSONDefaultLayer.providedBy(request)
+  False
+
+If we try to set a default skin and no one exist we will not fail but
+nothing happens
+
+  >>> from zope.publisher.skinnable import setDefaultSkin
+  >>> setDefaultSkin(request)
+
+Make sure our IJSONDefaultLayer provides the ISkinType interface.
+This is normaly done in a configure.zcml using the interface directive:
+
+  >>> ISkinType.providedBy(IJSONDefaultLayer)
+  False
+
+  >>> zope.interface.alsoProvides(IJSONDefaultLayer, ISkinType)
+  >>> ISkinType.providedBy(IJSONDefaultLayer)
+  True
+
+Let's define a default skin adatper which the setDefaulSkin can use. This
+adapter return our IJSONDefaultLayer. We also register this adapter within 
+``default`` as name:
+
+  >>> from zope.publisher.interfaces import IDefaultSkin
+  >>> def getDefaultJSONLayer(request):
+  ...     return IJSONDefaultLayer
+
+  >>> zope.component.provideAdapter(getDefaultJSONLayer,
+  ...     (IJSONRequest,), IDefaultSkin, name='default')
+
+  >>> setDefaultSkin(request)
+  >>> IJSONDefaultLayer.providedBy(request)
+  True
+
+When we register a default skin, without that the skin provides an ISkinType,
+the setDefaultSkin will raise a TypeError:
+
+
+  >>> from zope.interface import Interface
+  >>> class IMySkin(Interface):
+  ...     pass
+  >>> zope.component.provideAdapter(IMySkin, (IJSONRequest,), IDefaultSkin)
+  >>> setDefaultSkin(request)
+  Traceback (most recent call last):
+  ...
+  TypeError: Skin interface <InterfaceClass __builtin__.IMySkin> doesn't provide ISkinType
+
+The default skin must provide ISkinType:
+
+  >>> zope.interface.alsoProvides(IMySkin, ISkinType)
+  >>> ISkinType.providedBy(IMySkin)
+  True
+
+setDefaultSkin uses the custom layer interface instead of IJSONDefaultLayer:
+
+  >>> request = JSONRequest(StringIO(''), {})
+  >>> IMySkin.providedBy(request)
+  False
+
+  >>> IJSONDefaultLayer.providedBy(request)
+  False
+
+  >>> setDefaultSkin(request)
+
+  >>> IMySkin.providedBy(request)
+  True
+
+  >>> IJSONDefaultLayer.providedBy(request)
+  False
+
+Any interfaces that are directly provided by the request coming into this
+method are replaced by the applied layer/skin interface. This is important
+for our retry pattern which will ensure that we start with a clean request:
+
+  >>> request = JSONRequest(StringIO(''), {})
+  >>> class IFoo(Interface):
+  ...     pass
+
+  >>> zope.interface.directlyProvides(request, IFoo)
+  >>> IFoo.providedBy(request)
+  True
+
+  >>> setDefaultSkin(request)
+  >>> IFoo.providedBy(request)
+  False
+
+
+applySkin
+---------
+
+The applySkin method is able to apply any given skin. Let's define some custom
+skins:
+
+  >>> import pprint
+  >>> from zope.interface import Interface
+  >>> class ISkinA(Interface):
+  ...     pass
+
+  >>> zope.interface.directlyProvides(ISkinA, ISkinType)
+  >>> class ISkinB(Interface):
+  ...     pass
+
+  >>> zope.interface.directlyProvides(ISkinB, ISkinType)
+
+Let's start with a fresh request:
+
+  >>> request = JSONRequest(StringIO(''), {})
+
+Now we can apply the SkinA:
+
+  >>> from zope.publisher.skinnable import applySkin
+  >>> applySkin(request, ISkinA)
+  >>> pprint.pprint(list(zope.interface.providedBy(request).interfaces()))
+  [<InterfaceClass __builtin__.ISkinA>,
+   <InterfaceClass __builtin__.IJSONRequest>,
+   <InterfaceClass zope.publisher.interfaces.IRequest>]
+
+And if we apply ISkinB, ISkinA get removed at the same time ISkinB get applied:
+
+  >>> applySkin(request, ISkinB)
+  >>> pprint.pprint(list(zope.interface.providedBy(request).interfaces()))
+  [<InterfaceClass __builtin__.ISkinB>,
+   <InterfaceClass __builtin__.IJSONRequest>,
+   <InterfaceClass zope.publisher.interfaces.IRequest>]
+
+
+setDefaultSkin and applySkin
+----------------------------
+
+If we set a default skin and later apply a custom skin, the default skin get
+removed at the time the applySkin get called within a new ISkinType:
+
+  >>> request = JSONRequest(StringIO(''), {})
+
+Note, that our IMySkin is the default skin for IJSONRequest. We can aprove that
+by lookup an IDefaultSkin interface for our request:
+
+  >>> adapters = zope.component.getSiteManager().adapters
+  >>> default = adapters.lookup((zope.interface.providedBy(request),),
+  ...     IDefaultSkin, '')
+  >>> default
+  <InterfaceClass __builtin__.IMySkin>
+
+  >>> setDefaultSkin(request)
+  >>> IMySkin.providedBy(request)
+  True
+
+  >>> ISkinA.providedBy(request)
+  False
+
+Now apply our skin ISkinA. This should remove the IMySkin at the same time the
+ISkinA get applied:
+
+  >>> applySkin(request, ISkinA)
+  >>> IMySkin.providedBy(request)
+  False
+
+  >>> ISkinA.providedBy(request)
+  True
+
+
+SkinChangedEvent
+----------------
+
+Changing the skin on a request triggers the ISkinChangedEvent event:
+
+  >>> import zope.component
+  >>> from zope.publisher.interfaces import ISkinChangedEvent
+  >>> def receiveSkinEvent(event):
+  ...     print "Notified SkinEvent for:", event.request.__class__.__name__
+  >>> zope.component.provideHandler(receiveSkinEvent, (ISkinChangedEvent,))
+  >>> applySkin(request, ISkinA)
+  Notified SkinEvent for: JSONRequest

Modified: zope.publisher/trunk/src/zope/publisher/tests/test_baserequest.py
===================================================================
--- zope.publisher/trunk/src/zope/publisher/tests/test_baserequest.py	2009-03-08 20:54:11 UTC (rev 97669)
+++ zope.publisher/trunk/src/zope/publisher/tests/test_baserequest.py	2009-03-08 21:27:26 UTC (rev 97670)
@@ -93,8 +93,9 @@
 
     def test_retry_keeps_everything(self):
         """lowlevel test for retry (see #98440)"""
-        from zope.publisher.browser import TestRequest, setDefaultSkin
-        from zope.publisher.interfaces.browser import IDefaultSkin
+        from zope.publisher.browser import TestRequest
+        from zope.publisher.skinnable import setDefaultSkin
+        from zope.publisher.interfaces import IDefaultSkin
         from zope.publisher.interfaces.browser import IBrowserRequest
         from zope.publisher.interfaces.browser import IBrowserSkinType
         # create a retryable request

Modified: zope.publisher/trunk/src/zope/publisher/tests/test_browserrequest.py
===================================================================
--- zope.publisher/trunk/src/zope/publisher/tests/test_browserrequest.py	2009-03-08 20:54:11 UTC (rev 97669)
+++ zope.publisher/trunk/src/zope/publisher/tests/test_browserrequest.py	2009-03-08 21:27:26 UTC (rev 97670)
@@ -23,12 +23,11 @@
 from zope.publisher.http import HTTPCharsets
 from zope.publisher.browser import BrowserRequest
 from zope.publisher.interfaces import NotFound
-
-from zope.publisher.base import DefaultPublication
+from zope.publisher.interfaces import ISkinnable
 from zope.publisher.interfaces.browser import IBrowserApplicationRequest
 from zope.publisher.interfaces.browser import IBrowserRequest
 from zope.publisher.interfaces.browser import IBrowserPublication
-from zope.publisher.interfaces.browser import ISkinnable
+from zope.publisher.base import DefaultPublication
 
 from zope.publisher.tests.test_http import HTTPTests
 from zope.publisher.tests.publication import TestPublication

Added: zope.publisher/trunk/src/zope/publisher/tests/test_skinnable.py
===================================================================
--- zope.publisher/trunk/src/zope/publisher/tests/test_skinnable.py	                        (rev 0)
+++ zope.publisher/trunk/src/zope/publisher/tests/test_skinnable.py	2009-03-08 21:27:26 UTC (rev 97670)
@@ -0,0 +1,35 @@
+# -*- coding: latin-1 -*-
+##############################################################################
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""HTTP Publisher Tests
+
+$Id: test_http.py 96537 2009-02-14 15:13:58Z benji_york $
+"""
+
+import unittest
+import zope.testing
+
+
+def cleanUp(test):
+    zope.testing.cleanup.cleanUp()
+
+
+def test_suite():
+    return unittest.TestSuite(
+        zope.testing.doctest.DocFileSuite('../skinnable.txt',
+            setUp=cleanUp, tearDown=cleanUp))
+
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='test_suite')



More information about the Checkins mailing list