You are not logged in Log in Join
You are here: Home » Members » Lalo's page at Zope.org » scriptclass-dev » wikipage_view

Log in
Name

Password

 
 

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.