Python Methods User Guide Draft
Status
This document is not finalized. Please make editorial comments
about the draft in the UserGuideDiscussion rather than in the draft
document directly. We are about to finalize the draft, so speak
now or forever hold your peace! :^)
Introduction
All of Zope's capabilities are provided by methods, one way or
another. When you ask Zope for a URL, it first fetches an object
from the ZODB and then calls some method of that object. The methods
of built-in Zope objects are defined in Zope's source code, or in
Python classes stored in Product modules on the filesystem.
Additional object classes (and their methods) can be defined by
creating new filesystem Products, and this is appropriate when the
implementation of a class requires extensive access to resources
(such as the filesystem) which should be carefully protected.
Methods can also be defined through the web, by writing DTML or
Python code in special Method objects. The execution of this code
is carefully constrained, because the ability to edit it through the
insecure medium of the Web makes it untrustworthy. Such code must
not be allowed direct access to the filesystem, for example, or a
compromise of Zope security would compromise the entire user account
under which Zope is run. Web-editable code is also subject to Zope
security restrictions so that it can be created and edited by users
with varying levels of Zope access.
Python Methods are the kinds of web-editable objects which allow you
to use Python code in your Zope applications. They come in two
flavors, Unrestricted and Restricted. Unrestricted Python Methods
provide access to code stored in files in an Extensions directory,
external to the ZODB. This is merely a slightly simpler alternative
to writing code in a filesystem Product. Restricted Python Methods
store their code in the ZODB, in the actual Method object. They
therefore enjoy the advantages of all web-editable Zope objects, such
as versioning, history, and undo. They can be distributed by ZEO, and
copied or backed up along with all other Zope objects simply by
copying or backing up the ZODB storage. For the rest of this
document, PM will be used as shorthand for "Python Method", rPM
for "Restricted Python Method", and uPM for "Unrestricted Python Method"
Creating Python Methods
When creating a PM, you need to start by deciding three things: where
it should live, under what Id, and should it be Restricted or
Unrestricted.
A PM's "home" is the container in which you create it. This is
determined by its intended use, and by the objects it needs to operate
upon. For example, a widely used PM that performs simple calculations
on its parameters should probably be placed in the root folder, or in
some other easily accessed place. A PM that defines part of the
behavior of a ZClassx would, of course, live in the ZClassx. If you
place a PM in a Folder, it can act as a method for manipulating that
Folder.
The Id is less important. It simply needs to be a reasonably
descriptive name that doesn't clash with the names of other Zope
objects. Normally, it should start with a letter and consist entirely
of of letters, digits, and underscores. This makes it easy to refer to
from other code, but isn't strictly necessary.
uPMs are more awkward to use than Restricted PMsx,
but aren't subject to security restrictions (hence the name).
You would probably only use them if you had a serious need to get at
the internals of Zope or of other objects, or at filesystem resources.
You might also use them to protect code that must be kept secret.
Most of the time, you are likely to want to use rPMs.
Creation of PMsx is discussed in more detail at
CreateRestrictedPythonMethod and CreateUnrestrictedPythonMethod.
Writing Restricted Python Methods
rPMs behave much like normal Python functions, and have the same basic
parts. When you create an rPM, you start by giving it an id,
a parameter list, and a function body. The id is the name by which
the rPM is accessed from other Zope objects, and the parameter list
is a list of names, separated by commas, for data that will need to
be passed to the rPM. An rPM with id "spam" and parameter list
"x, y" could be called from other code be writing "spam(ham , 4)"
or "spam(x='ham', y=4)". The body consists of lines of Python code
which use the parameter names to perform some actions and/or compute
some result. http://www.python.org is a good place to start, if
you want to learn about programming in Python.
Restrictions on Python code
Python code in an rPM has a few restrictions:
"exec" statements are not allowed.
"import" statements can only import modules for which Zope security
information has been supplied.
some built-in functions, such as "open" and "eval", are removed
or restricted.
Also, the meaning of the "print" statement is changed. Printed
output is not sent to sys.stdout. Instead, it is collected in a
read-only variable named "printed". This allows code to use
"print", then return or otherwise process the printed text.
Notes for Experienced Python Programmers
The rPM actually constructs and compiles a Python function internally,
by indenting every line of the body a single space and adding the
"def f(params):" header line. The id of the rPM is not used as the
name of this internal function, since a Zope id need not be a valid
Python identifier, but tracebacks will show the proper name.
If you want to test whether two or more objects have the same type,
you must use the new builtin same_type function, since type is
not available.
Using Python Methods from DTML code
You can call a PM from DTML code in the same way that you would
call any other Zope method, by naming it or looking it up in the
namespace object. To call PM "spam" with no parameters, use
&dtml-spam; or <dtml-var name="spam">. To call it with
parameters, you must use a DTML expression such as
<dtml-var expr="spam(1, foo )">. Notice that unlike DTML
Methods, which require keyword parameters, PMsx can be called with
positional parameters. In other words, you don't have to specify the
parameter name for an argument, as long as you provide arguments in
the correct order.
An PM does not automatically find its parameters in the namespace
like DTML does, unless you specifically turn on this behavior in the
Binding Tab.
Python Method Access to Zope Objects
You cannot get a Zope object from a PM simply by using its name.
Instead, you must either pass the object to the PM as an argument,
or acquire the object from another object passed as a parameter or
named on the Binding Tab.
For example, given PM "spindle" with parameter "folder", if we
call "spindle(myFolder)" then the code in "spindle" can
use "folder.penguin" to acquire "penguin", or
"folder['index_html']" to access folder contents, or
"folder.manage_delObjects(['foo'])" to manipulate the folder.
Parameters are Not (Always) Enough
Explicitly passing a Zope object to a PM works well when you
already have (or can easily get) the object. Sometimes, however,
you want a PM to have access to its container, or to the object
on which it was invoked.
For example, suppose we are defining a
ZClassx "Spam" and wish to provide a method "getTitle" in Spam that
returns the title of an instance. A method which calls "getTitle"
will often do so by acquiring it, and so will not have a reference
to the actual instance.
For that matter, you may not trust the caller to pass the correct
instance object. In this case, you want some way to declare a
variable named "instance", guaranteed to contain the ZClassx instance
from which "getTitle" was acquired, no matter what parameters were
passed to it. Then you could use "return instance.title".
Now suppose that you want to provide a method named "getPathFromSpam"
that returns the path from the Spam instance to the object on which
the method is called. If we have a Spam instance "spam1" containing
Folder "f1", which contains object "myOb", we want a call to
"getPathFromSpam" from myOb to return "f1/myOb". In this case, we not
only want the "instance" variable, we want a "target" variable that
contains myOb.
The way you declare these special variables is with the Binding tab.
It allows you to give a PM access to various useful objects and
information by choosing a variable name. In the example above, you
would type "instance" into the Container binding field, and "target"
into the Context binding field. You would not include "instance" or
"target" in the parameter list of the PMsx. They are not parameters,
they are bound variables.
Conclusion
The implementation of Python Methods described in this document
is expected to be available in Zope 2.3 and higher.
|