[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