/usr/share/pyshared/zope/publisher/skinnable.txt is in python-zope.publisher 3.12.6-2.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 | =========
Skinnable
=========
Requests can provide skins. But what exactly is 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 registering different adapters.
That's a little bit much use of the word skin. Let's explain it in more
detail. A skin is an interface which provides an interface. This
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 building 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 let's look at a higher level. A skin is a concept which we can use
for providing different kinds of views, templates or other adapters
adapting a request. These skins are the key component for providing
different kind of application layers. A skin makes it possible for
an application to act very differently with each skin. Of course, that's
only the case at the interaction level where the request is involved.
But that's the case most of the time since we have a web application
server.
Another part of the skinnable concept is that an application can define
zero or more default skins. This is done with the IDefaultSkin
interface. Default skins can be defined for request interfaces or
implementations. Such a default skin can get overriden in a custom
setup. Overriding a skin can be done by using the defaultSkin directive
offered from zope.app.publication.zcml.
Why does a request need a default skin? If a request needs to provide
some pluggable concepts that require 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 its own
skin and register the custom adapter for this new project based skin.
This is very handy and allows developers to skip a complete
default-skin-based setup for a given request.
In general, this means a request interface and the request class that
implements the request interface only provides 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 adapters which the request implementation needs to have. This gives
us the option to replace the default skin within a custom skin and
provide custom adapters.
Our exmple will define a full request and all its components 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 traversal to a skin within a
skin namespace called ``skin``. This allows traversal to a method called
``applySkin`` which will apply a registered named skin. This means if we
register an ISkinType with a 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 gets started.
If a default skin is not available, the fallback default skin is 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. It registers plain
interfaces as adapters which are not adaptable. We have special code to
handle this case, which we will demonstrate below.
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
Now let's examine what can happen with our legacy case: an interface is
registered as an adapter.
>>> from zope.publisher.interfaces import IDefaultSkin
>>> sm = zope.component.getSiteManager()
>>> sm.registerAdapter(
... IJSONDefaultLayer, (IJSONRequest,), IDefaultSkin, name='default')
>>> request = JSONRequest(StringIO(''), {})
>>> IJSONDefaultLayer.providedBy(request)
False
>>> setDefaultSkin(request)
>>> IJSONDefaultLayer.providedBy(request)
True
What if the request already provides the interface?
>>> IJSONDefaultLayer.providedBy(request)
True
>>> setDefaultSkin(request)
>>> IJSONDefaultLayer.providedBy(request)
True
Now let's define a default skin adapter which the setDefaultSkin can use. This
adapter return our IJSONDefaultLayer. We also register this adapter within
``default`` as name:
>>> 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
|