scriptclass-dev
*This is the development (Wiki) versionof this How-To. For the stable (released) version, check out http://www.zope.org/Members/lalo/scriptclass - if you wish to spread the word about this How-To, please use the URL for the stable version, thank you.*
What
Scripts (found in Shared.DC.Scripts.Script
in your Zope >= 2.3.0 installation) are a new API for building Zope classes in Python-based Products. They were introduced originally for PythonScripts?, with the intent of eventually releasing the PerlScripts? Product based on this same API. Since then, Page Templates were developed on top of Scripts, and it proved a lot for the better.
This How-To aims to tutor a Python/Zope developer who can already develop Python-based Products and classes, to use the Script API.
Why and When
Because it's fun? ;-)
Seriously, why would you want to use this new API?
- If your object is some kind of script that executes code in some kind of language, you'll find this API gives you a lot of benefits, and is clearer.
- If you want to use the wonderful traverse_subpath offered by PythonScripts?, this is one of the easiest ways.
- If there is a good chance that your object will be called from inside DTML and Pyton and whatever-else and you want to provide a sane API, without yourobject(.None, ) and somesuch.
If you scored one point, you should think seriously. If you scored more than one, then this is definitely the API for you.
How
Okay, so you're still reading. Let's get our hands dirty.
Reference code
In case of doubt, you can read the sources to PythonScript
(Products.PythonScripts.PythonScript.PythonScript
) or ZopePageTemplate
(Products.PageTemplates.ZopePageTemplate.ZopePageTemplate
).
What to subclass
Your class has to be a subclass of Script
. This class is already a subclass of SimpleItem
, so if you wish, you can get away with subclassing only Script
. However, it's "educated" to also subclass Cacheable
and perhaps Historical
, and you may want to add PropertyManager
.
Manage options
It is conventional that the first tab edits the script, and the second one tests it.
Instead of redefining the others, you may want to simply add them, like in this example (taken from PythonScript.py
):
manage_options = ( {'label':'Edit', 'action':'ZPythonScriptHTML_editForm', 'help': ('PythonScripts', 'PythonScript_edit.stx')}, ) + BindingsUI.manage_options + ( {'label':'Test', 'action':'ZScriptHTML_tryForm', 'help': ('PythonScripts', 'PythonScript_test.stx')}, {'label':'Proxy', 'action':'manage_proxyForm', 'help': ('OFSP','DTML-DocumentOrMethod_Proxy.stx')}, ) + Historical.manage_options + SimpleItem.manage_options + \ Cacheable.manage_options
The "Try" tab
You can use the provided ZScriptHTML_tryForm
method for the "Try" tab, as shown above. This method is defined in the Script
class.
For this to work, you have to define a method with the signature 'ZScriptHTML?_tryParams(self)'; this method should return a list of strings, that will be used to input the parameters (arguments).
If type matters, you can use form variable type conversions in these strings. The user can also edit the "Parameter" inputs, to add these conversions. Just return something like ("name", "age:int")
and it will do.
Initializing the instance
Before you first call your object, you must initialize the bindings (if you don't know what they are, read the help for PythonScript and then come back.
You'll probably be surprised to know that there aren't any defaults for the bindings, so you'll have to set them up yourself. You do that by calling self.ZBindings_edit(defaultBindings)
(this method is inherited from Shared.DC.Scripts.Bindings.Bindings
), where defaultBindings
is a dictionary mapping internal name to visible name.
Well, actuall there is a set of standard bindings; they simply are not enforced by the code, you have to bind them yourself. They live at Shared.DC.Scripts.Bindings.Bindings.defaultBindings
, but can also be accessed (trough reexporting) as Shared.DC.Scripts.Script.Bindings.defaultBindings
, which is usually convenient. As of this writing, the definition is:
defaultBindings = {'name_context': 'context', 'name_container': 'container', 'name_m_self': 'script', 'name_ns': '', 'name_subpath': 'traverse_subpath'}
Both PythonScript? and ZopePageTemplate? store a "cooked" (intermediate code) version of the script in a volatile attribute, for performance reasons. It is probably a good idea to follow this pattern. In this case, it is advisable to "pre-cook" this representation in the initialization, too.
What to override
Of course, it's usually a good idea to override PUT()
, document_src()
, PrincipiaSearchSource()
and manage_FTPget()
(manage_FTPput()
is usually bound to PUT()
). Perhaps also 'manage_historyCompare()'; check the sources to PythonScript
or ZopePageTemplate
if you're into Historic
objects.
However, for the Script API itself, the only method you need to override is _exec()
. The actual signature is:
def _exec(self, bound_names, args, kw):
Here, bound_names
will be a mapping with a pre-existing namespace (that may have more than the usual bindings, if you're called via __render_with_namespace__()
), and args
and kw
have the usual meaning.
It is advisable to take any additional security measures you conceive here. For examples, look (again) at the source.
The _exec()
method should return the Python representation of the result of running the Script (usually a string, if it's going to be called from the web, but of course this can be anything).
Optional hooks
-
_editedBindings()
- this is called when the bindings change.
How to use your object from Python code
After all this work, your object will be treated just like a Python function. You can call it from Python Scripts or Page Templates or even DTML expr
Python expressions without any special, awkward parameters; just give it the parameters it wants, and the rest (bindings) will be obtained by the magic of the framework.
So, if your instance runs with no parameters, you can do just myInstance()
. If it requires name
and age
as in the example above, then just call myInstance("lalo", 26)
and you're set.