[PEAK] Does a PEAK-ized webware esist ?

Phillip J. Eby pje at telecommunity.com
Fri Jan 30 15:22:05 EST 2004


At 09:26 AM 1/30/04 -0600, wayne at larsen.st wrote:

> >>
> >>It would be great to get an example of how that would be constructed.
> >
> > Which?  The peak.web canonical way, or building an app out of resources?
> >
> >
>Well, since I learn best by examples, the more the better!  But seriously,
>just a more comprehensive peak.web canonical way would be extremely useful
>to me.

Okay.  The basic assumption is that you have a set of components that 
provide services (aka solution-domain components), and objects that 
represent the real-world entities that the services use/supply/interact 
with (problem-domain model objects).

Solution-domain components are usually built on binding.Component, and 
problem-domain objects are usually built on model.Element.  Both should be 
UI-independent, so that they can be reused with different UIs or in 
different applications altogether.

So, 'peak.web' is mostly mechanisms for layering a web UI atop your 
components and elements.  In essence, a web UI consists of two things:

* A traversal mechanism (known as the "path protocol")

* A rendering mechanism (known as the "page protocol")

'peak.web' uses PyProtocols adaptation to adapt your components and 
elements to these protocols.  In other words, you define traversal and 
rendering by creating adapters from your non-UI application to a web UI.

The default "path protocol" is 'web.IWebTraversable', and the default "page 
protocol" is 'web.IWebPage'.  (Technically, there is also an "error 
protocol" used for error handling, but it's less likely that you'll deal 
with it directly.)

Sometimes, it may happen that you are using a component in more than one 
application.  For example, if you are doing "enterprise" apps, your 
LoginAccount object might show up in several applications.  Sometimes, the 
user interface for a specific application needs to be different, or 
somewhat different than in other applications.  If you have this situation, 
you can create 'protocols.Variation()' objects for each app, based on the 
original path and page protocols.  Then, you can create different adapters 
between the UI-less base objects and the individual application.

That's all fairly advanced usage, of course, but I bring it up to point out 
*why* things are structured as they are.  However, if you just want to make 
simple apps with 'peak.web', none of this comes up: you simply deal 
directly with the 'IWebTraversable' and 'IWebPage' interfaces.

In fact, you probably won't deal much with 'IWebPage' directly, either, 
unless you want to implement your own templating type to use in place of 
'peak.web.templates', or implement some other sort of special resource type.

Instead, you'll mainly deal with the path protocol, primarily by 
subclassing 'web.Decorator', like this:

class MyDecorator(web.Decorator):

     """Adapt MyClass to add UI"""

     protocols.advise(
         instancesProvide = [IWebTraversable],
         factoryMethod = 'asTraversableFor',
         asAdapterForTypes = [MyClass],
     )

     security.allow(someMethod = security.Anybody)

     someMethod = web.bindResource('someMethod')


What the above means is that if you traverse to an instance of MyClass via 
the web, you'll be able to traverse to a sub-location 'someMethod'.  When 
that occurs, a "resource" for 'someMethod' will be located by the skinning 
system, and that resource will be adapted to the "page protocol" if it's 
the terminal element of the traversal.

I'll get back to resources in a second.  First, I'll touch on the use of 
peak.security to declare permissions.  There are previous lengthy 
explanations and examples of peak.security both here and on the Wiki, so 
I'll let you look at them instead of repeating them.  But, there is one 
thing to add: explicit security declarations on a decorator take precedence 
over declarations on the underlying object.  So, you can grant (or deny) 
access via a decorator to something that was undeclared or granted/denied 
on the underlying object.

Note also that decorators add or override attributes on the underlying 
object.  Ordinarily, an attribute of the underlying object is traversable, 
as long as the current user has rights to access that name on the 
object.  The traversed-to object will be adapted to the traversal protocol, 
and traversal will continue.

So, if you have no security declarations on your "normal" objects, then 
only attributes you add and declare access for on the decorator will be 
traversable.  If you have security declarations on the objects' attributes 
or methods, then they will be traversable, even if you don't create an 
explicit Decorator class.  In other words, if you don't define an explicit 
Decorator class, the base 'Decorator' class is automatically used instead.

If you're used to ZPublisher-style traversal, you may be wondering about 
__getitem__ support.  That's something you have to explicitly enable, using 
'ContainerAsTraversable'.  'ContainerAsTraversable' is a 'Decorator' 
subclass designed for objects that want their __getitem__ to be tried first 
for any traversal.  You can subclass it instead of Decorator if you want to 
decorate a container.  It is also used automatically by default for objects 
that are dictionaries (or 'dict' subclasses) and objects that implement 
storage.IDataManager or naming.IBasicContext.

When you access an item from a container, the name is not checked for 
security.  Instead, a "nameless check" on the retrieved object is 
performed, to determine whether you're allowed to traverse to it.

Let me see, what have I skimmed over?  Every web hit is treated as an 
"interaction": an object that wraps request, response, user, skin, and 
anything else you might want.  Literally.  You can subclass the 
web.Interaction base class and add a binding for 'shoppingCart', if you 
want, setting it up to say, look up a shopper ID cookie from the request, 
and then retrieve a cart object from the appropriate data manager.  The 
user and skin are implemented in similar fashion, as bindings that request 
data from an authentication service and skin service.

So what's a skin?  A skin is a collection of layers, that supply named 
resources.  They're used for selecting different UIs (e.g. desktop browser 
vs. handheld), different "themes" (visual designs), or even 
internationalization.  (If you don't care about these things, you'll simply 
leave the default skin binding alone, which requests the "default skin" 
from the skin service.)

Because skins have many "multidimensional" uses, you may have resources 
that need to be shared between skins.  That's where layers come in.  Layers 
are a logical set of resources.  They are stacked to form skins.  For 
example, you could stack up "englishLanguage", "blueIcons", and 
"desktopTemplates" resource layers to create a complete skin.  Resources 
are looked up from a skin's layers in order, until the resource is found.

But where do the resources themselves come from?  By default, it's files in 
directories, but you can override this for any layer, since it's you who 
sets up and names the layers in the first place (see '[peak.web.layers]' in 
'peak.ini').  Nominally, you'll do this with a 'ResourceDirectory', e.g:

[peak.web.layers]
englishLanguage = web.ResourceDirectory(filename='/somewhere/langstuff/en', 
isRoot=True)
blueIcons = web.ResourceDirectory(filename='/somewhere/images/blue', 
isRoot=True)
# ...etc

In order to prevent resource naming conflicts between components that are 
created and distributed separately, resource names always begin with a 
package prefix.  So, within each layer, there's going to be a per-package 
subdirectory that contains the resources for that package.  Thus, in our 
example above, we might have files like:

/somewhere/langstuff/en/peak.web/standard_error.pwt   # error template for 
our site
/somewhere/langstuff/en/myApp/someMethod.pwt          # someMethod for our 
decorator

(See, I told you we'd get back to 'someMethod' eventually.)  Notice, by the 
way, that we use the *dotted* package name in resource directories, to 
simplify creation and maintenance.  You can also see from this example that 
if PEAK itself is looking for the 'standard_error' template, you can 
override this, and still have your *own* 'standard_error' template that is 
entirely unrelated.

(By the way, I don't recommend that you translate an application by copying 
and editing the templates; there are better solutions possible with 
'gettext' and the like, although they are not well-integrated with 
'peak.web' as yet.  Normally, the internationalization layers would contain 
message catalogs as resources, and page templates would be in the UI-format 
or theme layers of an application that's this sophisticated.)

So, now that I've *completely* scared you off of peak.web...  :)  I'll reel 
this back in a little to show how none of this complexity comes into play 
unless you want it to.

For most applications, you don't care about skinning and theming and 
i18n.  How can you just do it simply?  Well, the "default" layer of the 
"default" skin is a different sort of ResourceDirectory.  Instead of using 
dotted package names to access resources for a package, it goes straight to 
the actual package directory.  So, when you first create an app, you just 
put the resources in the package, akin to the Zope 2 style of creating 
"product" packages.  These become the default resources for your UI 
components, and can of course be easily packaged and distributed via the 
distutils.  When somebody uses your components, they will get the default 
resources unless they override them with another layer in the default skin, 
or by creating additional skins.

There is one small catch to this packaging approach: you must explicitly 
list packages with resources in the 'peak.web.resource_packages' property 
namespace.  The DefaultLayer object only allows access to resources in 
packages that are declared there, to prevent unintentional publishing of 
packages' contents.

So what are resources, anyway?  They're objects.  'peak.web' doesn't care 
what they are, per se, unless you're traversing it or rendering it, in 
which case the usual rules apply.  Typically, though, you'll have some 
static file resources, and some dynamic ones.  You can map filenames and 
extensions to resource factories using 'resources.ini' in any directory 
where resources are contained.  Subdirectory configuration overrides parent 
directory configuration, and the global defaults for all resource 
directories are in 'resource_defaults.ini', in the 'peak.web' 
package.  (You can set a different location to load global defaults from, 
using the 'peak.web.resourceDefaultsIni' property.)

Resources, by the way, are supposed to be polymorphic.  What does that 
mean?  It means that a resource's name should NOT be based on its type.  If 
you name a resource 'somepage.html', then what are you going to do when you 
want it to be a PDF?  So, 'peak.web' encourages the use of type-free 
resource names.  Now, that doesn't mean the files don't have 
extensions.  It just means that the resource name you use in code, and the 
URLs you use in the web app, don't have to include the extensions.  As long 
as there is only one file in a resource directory with the given base name, 
the extension is unnecessary.

Whew.  I think I've left out a *lot* of detailed bits, like being able to 
bind to resources from other packages, and I've also skipped 
peak.web.templates entirely.  I haven't mentioned that the easy way to 
supply an alternative interaction class, auth service, skin service, etc. 
is to define them in a [Component Factories] section of an app's .ini.  I 
haven't dealt with transactions either, so I'll mention that interactions 
handle doing storage.beginTransaction() as well as commits and aborts for you.

What else?  Oh, how do you *start* an application.  Well, it's basically 
the same as any other PEAK application; you specify a factory URL such as 
'import:myapp.SomeClass', and then run it under 'peak CGI' or one of the 
other containers (like 'peak launch' to run a mini webserver on your local 
machine and launch the app in your browser).

Last, but not least, you will need Zope X3 installed in order to use 
peak.web, as we use its Request, Response, and "publisher" components 
within peak.web.

While not exactly examples, the above should give you a basic idea of the 
architecture of peak.web.  The only reason I describe it as "not ready for 
prime time" is that lots of fit-and-finish pieces are missing.  For 
example, there is no 'standard_error' template made currently.  There are 
few DOMlets available for peak.web.templates, and so on.  You can see the 
complete list of things I consider open issues for 'peak.web' in TODO.txt; 
if none of them affect you, feel free to experiment.




More information about the PEAK mailing list