=========
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
