[Twisted-commits] Major changes to the capabilities of the static web server, in an
dp CVS
twisted-python@twistedmatrix.com
Wed, 17 Apr 2002 14:25:09 -0500
Modified files:
Twisted/ChangeLog 1.55 1.56
Twisted/bin/twistd 1.40 1.41
Twisted/twisted/plugins.tml 1.16 1.17
Twisted/twisted/spread/refpath.py 1.1 1.2
Twisted/twisted/tap/web.py 1.21 1.22
Twisted/twisted/web/domtemplate.py None 1.1
Twisted/twisted/web/trp.py None 1.1
Twisted/twisted/web/resource.py 1.11 1.12
Twisted/twisted/web/script.py 1.5 1.6
Twisted/twisted/web/static.py 1.29 1.30
Twisted/twisted/web/twcgi.py 1.10 1.11
Twisted/twisted/web/widgets.py 1.39 1.40
Twisted/twisted/web/blog/BlogPost.py None 1.1
Twisted/twisted/web/blog/BlogPostResourceAdapter.py None 1.1
Twisted/twisted/web/blog/__init__.py None 1.1
Log message:
Major changes to the capabilities of the static web server, in an
attempt to be able to use Twisted instead of Zope at work; my plan is to
capture many of the conveniences of Zope without the implicitness and
complexity that comes with working around implicit behavior when it fails.
See changelog for details.
ViewCVS links:
Error generating URL for file '{'filename': 'ChangeLog', 'r2': (1, 56), 'r1': (1, 55), 'pathname': 'Twisted'}'
http://twistedmatrix.com/users/jh.twistd/viewcvs/cgi/viewcvs.cgi/bin/twistd.diff?r1=text&tr1=1.40&r2=text&tr2=1.41&cvsroot=Twisted
http://twistedmatrix.com/users/jh.twistd/viewcvs/cgi/viewcvs.cgi/twisted/plugins.tml.diff?r1=text&tr1=1.16&r2=text&tr2=1.17&cvsroot=Twisted
http://twistedmatrix.com/users/jh.twistd/viewcvs/cgi/viewcvs.cgi/twisted/spread/refpath.py.diff?r1=text&tr1=1.1&r2=text&tr2=1.2&cvsroot=Twisted
http://twistedmatrix.com/users/jh.twistd/viewcvs/cgi/viewcvs.cgi/twisted/tap/web.py.diff?r1=text&tr1=1.21&r2=text&tr2=1.22&cvsroot=Twisted
http://twistedmatrix.com/users/jh.twistd/viewcvs/cgi/viewcvs.cgi/twisted/web/domtemplate.py.diff?r1=text&tr1=None&r2=text&tr2=1.1&cvsroot=Twisted
http://twistedmatrix.com/users/jh.twistd/viewcvs/cgi/viewcvs.cgi/twisted/web/trp.py.diff?r1=text&tr1=None&r2=text&tr2=1.1&cvsroot=Twisted
http://twistedmatrix.com/users/jh.twistd/viewcvs/cgi/viewcvs.cgi/twisted/web/resource.py.diff?r1=text&tr1=1.11&r2=text&tr2=1.12&cvsroot=Twisted
http://twistedmatrix.com/users/jh.twistd/viewcvs/cgi/viewcvs.cgi/twisted/web/script.py.diff?r1=text&tr1=1.5&r2=text&tr2=1.6&cvsroot=Twisted
http://twistedmatrix.com/users/jh.twistd/viewcvs/cgi/viewcvs.cgi/twisted/web/static.py.diff?r1=text&tr1=1.29&r2=text&tr2=1.30&cvsroot=Twisted
http://twistedmatrix.com/users/jh.twistd/viewcvs/cgi/viewcvs.cgi/twisted/web/twcgi.py.diff?r1=text&tr1=1.10&r2=text&tr2=1.11&cvsroot=Twisted
http://twistedmatrix.com/users/jh.twistd/viewcvs/cgi/viewcvs.cgi/twisted/web/widgets.py.diff?r1=text&tr1=1.39&r2=text&tr2=1.40&cvsroot=Twisted
http://twistedmatrix.com/users/jh.twistd/viewcvs/cgi/viewcvs.cgi/twisted/web/blog/BlogPost.py.diff?r1=text&tr1=None&r2=text&tr2=1.1&cvsroot=Twisted
http://twistedmatrix.com/users/jh.twistd/viewcvs/cgi/viewcvs.cgi/twisted/web/blog/BlogPostResourceAdapter.py.diff?r1=text&tr1=None&r2=text&tr2=1.1&cvsroot=Twisted
http://twistedmatrix.com/users/jh.twistd/viewcvs/cgi/viewcvs.cgi/twisted/web/blog/__init__.py.diff?r1=text&tr1=None&r2=text&tr2=1.1&cvsroot=Twisted
Index: Twisted/twisted/web/twcgi.py
diff -u Twisted/twisted/web/twcgi.py:1.10 Twisted/twisted/web/twcgi.py:1.11
--- Twisted/twisted/web/twcgi.py:1.10 Sun Apr 14 06:02:18 2002
+++ Twisted/twisted/web/twcgi.py Wed Apr 17 12:25:06 2002
@@ -84,7 +84,7 @@
"SCRIPT_FILENAME": self.filename,
"REQUEST_URI": request.uri,
"PYTHONPATH" : python_path
- }
+ }
client = request.getClient()
if client is not None:
@@ -133,11 +133,19 @@
def runProcess(self, env, request):
CGIProcess(self.filter, [self.filename], env, os.path.dirname(self.filename), request)
-class PHPScript(FilteredScript):
+class PHP3Script(FilteredScript):
"""I am a FilteredScript that uses the default PHP3 command on most systems.
"""
filter = '/usr/bin/php3'
+class PHPScript(FilteredScript):
+ """I am a FilteredScript that uses the PHP command on most systems.
+ Sometimes, php wants the path to itself as argv[0]. This is that time.
+ """
+ filter = '/usr/bin/php'
+ def runProcess(self, env, request):
+ CGIProcess(self.filter, [self.filter, self.filename], env, os.path.dirname(self.filename), request)
+
class CGIProcess(process.Process, pb.Viewable):
handling_headers = 1
headers_written = 0
Index: Twisted/twisted/web/script.py
diff -u Twisted/twisted/web/script.py:1.5 Twisted/twisted/web/script.py:1.6
--- Twisted/twisted/web/script.py:1.5 Fri Apr 5 13:13:34 2002
+++ Twisted/twisted/web/script.py Wed Apr 17 12:25:06 2002
@@ -30,6 +30,17 @@
del cStringIO
import traceback
+def PyCompiler(path):
+ """
+ I am a normal py file which will define a "resource" global upon completion
+ The resource global should be an instance of Resource, and will be returned
+ """
+ globals = {}
+
+ execfile(path, globals, globals)
+
+ return globals['resource']
+
class PythonScript(resource.Resource):
"""I am an extremely simple dynamic resource; an embedded python script.
Index: Twisted/twisted/tap/web.py
diff -u Twisted/twisted/tap/web.py:1.21 Twisted/twisted/tap/web.py:1.22
--- Twisted/twisted/tap/web.py:1.21 Wed Apr 17 09:58:03 2002
+++ Twisted/twisted/tap/web.py Wed Apr 17 12:25:06 2002
@@ -21,7 +21,7 @@
import string, os
# Twisted Imports
-from twisted.web import server, static, twcgi, script, test, distrib
+from twisted.web import server, static, twcgi, script, test, distrib, trp
from twisted.internet import tcp
from twisted.python import usage, reflect
from twisted.spread import pb
@@ -74,8 +74,11 @@
self.opts['root'] = static.File(os.path.abspath(path))
self.opts['root'].processors = {
'.cgi': twcgi.CGIScript,
- '.php3': twcgi.PHPScript,
- '.epy': script.PythonScript
+ '.php3': twcgi.PHP3Script,
+ '.php': twcgi.PHPScript,
+ '.epy': script.PythonScript,
+ '.py': script.PyCompiler,
+ '.trp': trp.ResourceUnpickler,
}
def opt_static(self, path):
@@ -137,4 +140,3 @@
pb.BrokerFactory(distrib.ResourcePublisher(site)))
else:
app.listenTCP(int(config.opts['port']), site)
-
Index: Twisted/bin/twistd
diff -u Twisted/bin/twistd:1.40 Twisted/bin/twistd:1.41
--- Twisted/bin/twistd:1.40 Tue Apr 9 16:33:49 2002
+++ Twisted/bin/twistd Wed Apr 17 12:25:05 2002
@@ -28,7 +28,7 @@
### end of preamble
from twisted import copyright
-from twisted.python import usage, util, runtime, register
+from twisted.python import usage, util, runtime, register, plugin
from twisted.lumberjack import logfile
util.addPluginDir()
@@ -191,6 +191,16 @@
except KeyError:
log.msg("Error - python file %s must set a variable named 'application', an instance of twisted.internet.app.Application. No such variable was found!" % repr(pyfile))
sys.exit()
+
+# Load any view plugins which have been registered in plugins.tml file
+# This needs to be moved to an event which occurs on web server startup
+# Once glyph is done with the Reactors
+plugins = plugin.getPlugIns('view')
+for plug in plugins:
+ try:
+ plug.load()
+ except Exception, e:
+ print "Loading view %s failed. %s" % (plug, e)
print "Loaded."
initRun = 1
Index: Twisted/twisted/plugins.tml
diff -u Twisted/twisted/plugins.tml:1.16 Twisted/twisted/plugins.tml:1.17
--- Twisted/twisted/plugins.tml:1.16 Fri Apr 12 12:55:50 2002
+++ Twisted/twisted/plugins.tml Wed Apr 17 12:25:05 2002
@@ -111,3 +111,13 @@
"twisted.tap.coil",
description="automated TAP building",
type="tap")
+
+## View modules that need to be loaded on startup so they can
+## call twisted.python.components.registerAdapter
+
+register("Blog Post web view adapter",
+ "twisted.web.blog.BlogPostResourceAdapter",
+ description="A blog post which can be viewed/edited ttw, via ftp, etc",
+ type="view")
+
+
Index: Twisted/ChangeLog
diff -u Twisted/ChangeLog:1.55 Twisted/ChangeLog:1.56
--- Twisted/ChangeLog:1.55 Wed Apr 17 09:58:00 2002
+++ Twisted/ChangeLog Wed Apr 17 12:25:04 2002
@@ -1,3 +1,44 @@
+2002-04-17 Donovan Preston <dp@twistedmatrix.com>
+
+ * Major changes to the capabilities of the static web server, in an
+ attempt to be able to use Twisted instead of Zope at work; my plan is to
+ capture many of the conveniences of Zope without the implicitness and
+ complexity that comes with working around implicit behavior when it fails.
+
+ 1) .trp and .rpy support in the static web server:
+ Very simple handlers to allow you to easily add Resource objects
+ dynamically to a running server, by merely changing files on the
+ filesystem.
+ An .rpy file will be executed, and if a "resource" variable exists upon the
+ execution's completion, it will be returned.
+ A .trp file (twisted resource pickle) will be unpickled and returned. An
+ object unpickled from a .trp should either implement IResource itself,
+ or have a registered adapter in twisted.python.components.
+
+ 2) Acquisition:
+ As resources are being looked up by repeated calls to getChild, this
+ change creates instances of
+ twisted.spread.refpath.PathReferenceAcquisitionContext and puts
+ them in the request as "request.pathRef"
+ Any method that has an instance of the request can then climb up
+ the parent tree using "request.pathRef['parentRef']['parentRef']
+ PathReferenceAcquisitionContext instances can be dereferenced to the
+ actual object using getObject
+ Convenience method: "locate" returns a PathReference to first place
+ in the parent heirarchy a name is seen
+ Convenience method: "acquire" somewhat like Zope acquisition;
+ mostly untested, may need fixes
+
+ 3) DOM-based templating system:
+ A new templating system that allows python scripts to use the DOM
+ to manipulate the HTML node tree. Loosely based on Enhydra.
+ Subclasses of twisted.web.domtemplate.DOMTemplate can override
+ the templateFile attribute and the getTemplateMethods method;
+ ultimately, while templateFile is being parsed, the methods
+ specified will be called with instances of xml.dom.mindom.Node
+ as the first parameter, allowing the python code to manipulate
+ (see twisted.web.blog for an example)
+
2002-04-17 Chris Armstrong <carmstro@twistedmatrix.com>
* twisted/web/static.py, twisted/tap/web.py: Added a new feature
Index: Twisted/twisted/web/resource.py
diff -u Twisted/twisted/web/resource.py:1.11 Twisted/twisted/web/resource.py:1.12
--- Twisted/twisted/web/resource.py:1.11 Mon Apr 8 03:12:54 2002
+++ Twisted/twisted/web/resource.py Wed Apr 17 12:25:06 2002
@@ -19,9 +19,11 @@
# System Imports
+from twisted.spread.refpath import PathReferenceAcquisitionContext
from twisted.python import roots, components
from twisted.coil import coil
+from copy import copy
class IResource(components.Interface):
"""A web resource."""
@@ -128,9 +130,13 @@
retrieve my appropriate child or grandchild to display.
"""
res = self
+ parentRef = PathReferenceAcquisitionContext([], self, None)
while request.postpath and not res.isLeaf:
pathElement = request.postpath.pop(0)
request.prepath.append(pathElement)
+ request.pathRef = PathReferenceAcquisitionContext(copy(request.prepath), self, parentRef)
+ request.pathRef['name'] = pathElement
+ parentRef = request.pathRef
res = res.getChildWithDefault(pathElement, request)
return res
Index: Twisted/twisted/web/static.py
diff -u Twisted/twisted/web/static.py:1.29 Twisted/twisted/web/static.py:1.30
--- Twisted/twisted/web/static.py:1.29 Wed Apr 17 01:00:42 2002
+++ Twisted/twisted/web/static.py Wed Apr 17 12:25:06 2002
@@ -37,7 +37,7 @@
# Twisted Imports
from twisted.protocols import http
-from twisted.python import threadable, log
+from twisted.python import threadable, log, components
from twisted.internet import abstract
from twisted.spread import pb
from twisted.persisted import styles
@@ -189,10 +189,19 @@
p, ext = os.path.splitext(newpath)
processor = self.processors.get(ext)
if processor:
- return processor(newpath)
+ p = processor(newpath)
+ if components.implements(p, resource.IResource):
+ return p
+ else:
+ adapter = components.getAdapter(p, resource.IResource, None)
+ if not adapter:
+ raise "%s instance does not implement IResource, and there is no registered adapter." % result.__class__
+ return adapter
+
f = File(newpath, self.defaultType, self.allowExt)
f.processors = self.processors
f.indexNames = self.indexNames[:]
+
return f
@@ -258,8 +267,28 @@
# and make sure the connection doesn't get closed
return server.NOT_DONE_YET
+ def listNames(self):
+ if not os.path.isdir(self.path): return []
+ directory = os.listdir(self.path)
+ directory.sort()
+ return directory
+
+ def listEntities(self):
+ return map(lambda fileName, self=self: File(os.path.join(self.path, fileName)), self.listNames())
-
+ def putChild(self, name, child):
+ if not os.path.isdir(self.path):
+ resource.Resource.putChild(self, name, child)
+ # xxx use a file-extension-to-save-function dictionary instead
+ fl = open(os.path.join(self.path, name), 'w')
+ if type(child) == type(""):
+ fl.write(child)
+ else:
+ from pickle import Pickler
+ pk = Pickler(fl)
+ pk.dump(child)
+ fl.close()
+
class FileTransfer(pb.Viewable):
"""
A class to represent the transfer of a file over the network.
Index: Twisted/twisted/spread/refpath.py
diff -u Twisted/twisted/spread/refpath.py:1.1 Twisted/twisted/spread/refpath.py:1.2
--- Twisted/twisted/spread/refpath.py:1.1 Tue Apr 16 15:49:09 2002
+++ Twisted/twisted/spread/refpath.py Wed Apr 17 12:25:05 2002
@@ -14,7 +14,8 @@
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-from flavors import Referenceable
+from flavors import Referenceable, Viewable
+from copy import copy
### "Server"-side objects
@@ -36,6 +37,47 @@
o = o.getChild(p, self)
return o
+class PathReferenceAcquisitionContext(PathReferenceContext):
+ def __init__(self, path, root, parentRef):
+ PathReferenceContext.__init__(self, path, root)
+ self.metadata['parentRef'] = parentRef
+
+ def __lookup(self, name, acquire=0):
+ obRef = self
+ ob = self.getObject()
+ found = 0
+ while not found:
+ if acquire and hasattr(ob, name):
+ retVal = getattr(ob, name)
+ break
+ elif hasattr(ob, 'listNames') and name in ob.listNames():
+ if acquire:
+ retVal = ob.getChild(name)
+ else:
+ foundPath = copy(obRef.path)
+ foundPath.append(name)
+ retVal = PathReferenceAcquisitionContext(foundPath, obRef.root, obRef)
+ break
+
+ obRef = obRef['parentRef']
+ ob = obRef.getObject()
+ else:
+ raise AttributeError, "%s not found." % name
+ return retVal
+
+ def locate(self, name):
+ """
+ Get a reference to an object with the given name which is somewhere
+ on the path above us.
+ """
+ return self.__lookup(name)
+
+ def acquire(self, name):
+ """
+ Look for an attribute or element by name in all of our parents
+ """
+ return self.__lookup(name, acquire=1)
+
class PathReference:
def __init__(self):
self.children = {}
@@ -51,7 +93,7 @@
obj = ctx.getObject()
return apply(getattr(obj, "%s_%s" % (self.prefix, name)), args, kw)
-class PathReferenceContextDirectory(Referenceable)
+class PathReferenceContextDirectory(Referenceable):
def __init__(self, root, prefix="remote"):
self.root = root
self.prefix = prefix
Index: Twisted/twisted/web/widgets.py
diff -u Twisted/twisted/web/widgets.py:1.39 Twisted/twisted/web/widgets.py:1.40
--- Twisted/twisted/web/widgets.py:1.39 Tue Apr 9 16:37:12 2002
+++ Twisted/twisted/web/widgets.py Wed Apr 17 12:25:06 2002
@@ -76,7 +76,7 @@
if hasattr(i, "__html__"):
s = i.__html__()
else:
- s = '<CODE>'+html.escape(repr(i))+'</code>'
+ s = '<code>'+html.escape(repr(i))+'</code>'
return '''<table bgcolor="#cc7777"><tr><td><b>%s</b> instance</td></tr>
<tr bgcolor="#ff9999"><td>%s</td></tr>
</table>
@@ -268,33 +268,33 @@
def htmlFor_hidden(write, name, value):
- write('<INPUT TYPE="hidden" NAME="%s" VALUE="%s">' % (name, value))
+ write('<INPUT TYPE="hidden" NAME="%s" VALUE="%s" />' % (name, value))
def htmlFor_file(write, name, value):
- write('<INPUT SIZE=60 TYPE="file" NAME="%s">' % name)
+ write('<INPUT SIZE="60" TYPE="file" NAME="%s" />' % name)
def htmlFor_string(write, name, value):
- write('<INPUT SIZE=60 TYPE="text" NAME="%s" VALUE="%s">' % (name, value))
+ write('<INPUT SIZE="60" TYPE="text" NAME="%s" VALUE="%s" />' % (name, value))
def htmlFor_password(write, name, value):
- write('<INPUT SIZE=60 TYPE="password" NAME=%s>' % name)
+ write('<INPUT SIZE="60" TYPE="password" NAME="%s" />' % name)
def htmlFor_text(write, name, value):
- write('<TEXTAREA COLS="60" ROWS="10" NAME="%s" WRAP="virtual">%s</textarea>' % (name, value))
+ write('<textarea COLS="60" ROWS="10" NAME="%s" WRAP="virtual">%s</textarea>' % (name, value))
def htmlFor_menu(write, name, value, allowMultiple=False):
"Value of the format [(optionName, displayName[, selected]), ...]"
- write(' <SELECT NAME="%s"%s>\n' %
+ write(' <select NAME="%s"%s>\n' %
(name, (allowMultiple and " multiple") or ''))
for v in value:
optionName, displayName, selected = util.padTo(3, v)
selected = (selected and " selected") or ''
- write(' <OPTION VALUE="%s"%s>%s</option>\n' %
+ write(' <option VALUE="%s"%s>%s</option>\n' %
(optionName, selected, displayName))
if not value:
- write(' <OPTION VALUE=""></option>\n')
+ write(' <option VALUE=""></option>\n')
write(" </select>\n")
def htmlFor_multimenu(write, name, value):
@@ -304,16 +304,16 @@
def htmlFor_checkbox(write, name, value):
"A checkbox."
if value:
- value = 'checked'
+ value = 'checked = "1"'
else:
value = ''
- write('<INPUT TYPE="checkbox" NAME="__checkboxes__" VALUE="%s" %s>\n' % (name, value))
+ write('<INPUT TYPE="checkbox" NAME="__checkboxes__" VALUE="%s" %s />\n' % (name, value))
def htmlFor_checkgroup(write, name, value):
"A check-group."
for optionName, displayName, checked in value:
- checked = (checked and 'checked') or ''
- write('<INPUT TYPE="checkbox" NAME="%s" VALUE="%s" %s>%s<br>\n' % (name, optionName, checked, displayName))
+ checked = (checked and 'checked = "1"') or ''
+ write('<INPUT TYPE="checkbox" NAME="%s" VALUE="%s" %s />%s<br />\n' % (name, optionName, checked, displayName))
class FormInputError(Exception):
pass
@@ -472,8 +472,8 @@
def format(self, form, write, request):
"""I display an HTML FORM according to the result of self.getFormFields.
"""
- write('<FORM ENCTYPE="multipart/form-data" METHOD="post" ACTION="%s">\n'
- '<TABLE BORDER="0">\n' % (self.actionURI or request.uri))
+ write('<form ENCTYPE="multipart/form-data" METHOD="post" ACTION="%s">\n'
+ '<table BORDER="0">\n' % (self.actionURI or request.uri))
for field in form:
if len(field) == 5:
@@ -481,22 +481,22 @@
else:
inputType, displayName, inputName, inputValue = field
description = ""
- write('<TR>\n<TD ALIGN="right" VALIGN="top"><B>%s</b></td>\n'
- '<TD VALIGN="%s">\n' %
+ write('<tr>\n<td ALIGN="right" VALIGN="top"><B>%s</B></td>\n'
+ '<td VALIGN="%s">\n' %
(displayName, ((inputType == 'text') and 'top') or 'middle'))
self.formGen[inputType](write, inputName, inputValue)
- write('\n<br>\n<font size="-1">%s</font></td>\n</tr>\n' % description)
+ write('\n<br />\n<font size="-1">%s</font></td>\n</tr>\n' % description)
- write('<TR><TD></TD><TD ALIGN="left"><hr>\n')
+ write('<tr><td></td><td ALIGN="left"><hr />\n')
for submitName in self.submitNames:
- write('<INPUT TYPE="submit" NAME="submit" VALUE="%s">\n' % submitName)
+ write('<INPUT TYPE="submit" NAME="submit" VALUE="%s" />\n' % submitName)
write('</td></tr>\n</table>\n'
- '<INPUT TYPE="hidden" NAME="__formtype__" VALUE="%s">\n'
+ '<INPUT TYPE="hidden" NAME="__formtype__" VALUE="%s" />\n'
% (str(self.__class__)))
fid = self.getFormID()
if fid:
- write('<INPUT TYPE="hidden" NAME="__formid__" VALUE="%s">\n' % fid)
+ write('<INPUT TYPE="hidden" NAME="__formid__" VALUE="%s" />\n' % fid)
write("</form>\n")
def getFormID(self):
@@ -530,7 +530,7 @@
The remainder of my arguments must be correctly named. They will each be named after one of the
"""
- write("Submit: %s <br> %s" % (submit, html.PRE(pprint.PrettyPrinter().pformat(kw))))
+ write("<pre>Submit: %s <br /> %s</pre>" % (submit, html.PRE(pprint.PrettyPrinter().pformat(kw))))
def _doProcess(self, form, write, request):
"""(internal) Prepare arguments for self.process.
@@ -580,7 +580,7 @@
By default, this will make the message appear in red, bold italics.
"""
- return '<FONT COLOR=RED><B><I>%s</i></b></font><br>\n' % error
+ return '<font COLOR=RED><B><I>%s</i></b></font><br />\n' % error
def shouldProcess(self, request):
args = request.args