Evan Simpson wrote: [snip intro on PythonMethod] Sounds great so far. I'd love to take a look at this and try to help you out.
Thoughts so far:
1. Must store a PythonMethod as raw text for editing purposes. Compile/hack on each load, or try to store the compiled function for speed?
Recompile/rehack each time the PythonMethod text is edited. This doesn't make anything extra difficult, I think? Take a look at XML Document (get it from CVS) for an example on how that's done. Actually, I think normal DTML Methods and such do the same, as Zope complains if you make a mistake with the DTML tags, I just am more familiar with the XML Document sources.
2. Allow simple assignment, and no other kind. "x = anything" is legal, but not "x[0] = ...", "x.spam = ...", "x() = ...", etc. Tuple/list unpacking might also be allowed.
Huh, why not? As long as you're manipulating a list or tuple created locally in the DTML method, that is. It gets trickier once you allow DTML methods to manipulate lists that come from Zope -- somehow those would need to be protected against meddling. But I think the DTML expression machinery already does some of this, right?
3. Disallow "del" and "exec" entirely.
Why disallow 'del'? exec, fine with me, though it could be made to work by passing anything that exec wants to execute on to the same security mechanisms as the PythonMethod itself. But that might be cumbersome, and 'exec' is not really very essential for me, at least. I rarely if ever use it.
4. Restrict "import" to modules/packages found in a particular location, so that 'safe' modules can be used.
I haven't thought about this enough to comment, but we should take a look at the bastion and restricted execution modules of Python.
5. Allow "while" and "for". Insert 'escape code' at the bottom of every loop, to allow PythonMethods to 'time out' after some elapsed time or loop count.
Make sure this works with 'continue' and 'break' too, tricky, tricky. :) If PythonMethods time out they should give an exception, I imagine. Also recursive functions can cause pain. Or perhaps instead of doing this, it's easier to do something like what was mentioned on the Python Microthreads discussion on comp.lang.python earlier. Basically just restrict the entire PythonMethod thread to a set amount of 'ticks'. Once the ticks are up and the Method didn't finish properly, throw an exception, or something. I've seen this work in programmable muds just fine -- if a process happens to run out of ticks, it just fails, and the mud continues running.
6. Allow "print" to be used to generate rendered output by replacing it with a function that accumulates text. If anything other than literal "None" is returned, the collected "print" text is discarded, otherwise it becomes the return value (on "return", "return None", and on implicit return, but not on "x=None; return x", for example).
Hm, seems a bit too implicit for me. We have two separate reasons for PythonMethods here: * Replacement of External Methods * Replacement of DTML methods/documents Either we replace these with two different kinds of PythonMethod, or we can choose for something explicit to return whatever's been sent to sys.stdout during execution of the method. Perhaps something like: return accumulated() Or something more complicated. I posted a message with a prototype on this type of thing a while back to this list. Just look for the messages with my name. :) How does Zope do it itself? DTML docs/methods return their rendered contents, when called. SQL methods return a list of results. External Methods can return a list or rendered contents. Digicool folks, correct me if I'm wrong, but I think Zope objects just return whatever they like; a string with rendered contents, or a list, or nothing at all, or something else. How that information is used depends on how you call the objects (with #var, or #call, or #in, etc). PythonMethods should just mimic that.
7. Either disallow "global", or redirect access to globals into a controlled namespace.
Disallow global, I'd say.
8. Provide the Zope standard builtins (and tags?) as builtins.
Again I refer you to my sample/simplistic implementation posted earlier to this list. Regards, Martijn