summaryrefslogtreecommitdiff
path: root/paste/webkit
diff options
context:
space:
mode:
Diffstat (limited to 'paste/webkit')
-rw-r--r--paste/webkit/FakeWebware/MiscUtils/CSVJoiner.py21
-rw-r--r--paste/webkit/FakeWebware/MiscUtils/CSVParser.py265
-rw-r--r--paste/webkit/FakeWebware/MiscUtils/DBPool.py128
-rw-r--r--paste/webkit/FakeWebware/MiscUtils/DataTable.py804
-rw-r--r--paste/webkit/FakeWebware/MiscUtils/DateInterval.py70
-rw-r--r--paste/webkit/FakeWebware/MiscUtils/DictForArgs.py209
-rw-r--r--paste/webkit/FakeWebware/MiscUtils/Error.py50
-rw-r--r--paste/webkit/FakeWebware/MiscUtils/Funcs.py325
-rw-r--r--paste/webkit/FakeWebware/MiscUtils/M2PickleRPC.py67
-rw-r--r--paste/webkit/FakeWebware/MiscUtils/MixIn.py70
-rw-r--r--paste/webkit/FakeWebware/MiscUtils/NamedValueAccess.py570
-rw-r--r--paste/webkit/FakeWebware/MiscUtils/ParamFactory.py22
-rw-r--r--paste/webkit/FakeWebware/MiscUtils/PickleCache.py220
-rw-r--r--paste/webkit/FakeWebware/MiscUtils/PickleRPC.py428
-rw-r--r--paste/webkit/FakeWebware/MiscUtils/PropertiesObject.py155
-rw-r--r--paste/webkit/FakeWebware/MiscUtils/__init__.py68
-rw-r--r--paste/webkit/FakeWebware/README.txt7
-rw-r--r--paste/webkit/FakeWebware/WebKit/HTTPServlet.py1
-rw-r--r--paste/webkit/FakeWebware/WebKit/Page.py1
-rw-r--r--paste/webkit/FakeWebware/WebKit/__init__.py1
-rw-r--r--paste/webkit/FakeWebware/WebUtils/Funcs.py148
-rw-r--r--paste/webkit/FakeWebware/WebUtils/__init__.py1
-rw-r--r--paste/webkit/FakeWebware/__init__.py1
-rw-r--r--paste/webkit/__init__.py1
-rw-r--r--paste/webkit/examples/EchoServlet.py57
-rw-r--r--paste/webkit/examples/__init__.py1
-rw-r--r--paste/webkit/test_wkfixture.py66
-rw-r--r--paste/webkit/wkapplication.py18
-rw-r--r--paste/webkit/wkcommon.py187
-rw-r--r--paste/webkit/wkrequest.py344
-rw-r--r--paste/webkit/wkresponse.py267
-rw-r--r--paste/webkit/wkservlet.py430
-rw-r--r--paste/webkit/wksession.py47
-rw-r--r--paste/webkit/wktransaction.py96
-rw-r--r--paste/webkit/wsgiwebkit.py67
35 files changed, 5213 insertions, 0 deletions
diff --git a/paste/webkit/FakeWebware/MiscUtils/CSVJoiner.py b/paste/webkit/FakeWebware/MiscUtils/CSVJoiner.py
new file mode 100644
index 0000000..2bc13a9
--- /dev/null
+++ b/paste/webkit/FakeWebware/MiscUtils/CSVJoiner.py
@@ -0,0 +1,21 @@
+import types
+
+
+def joinCSVFields(fields):
+ """
+ Returns a CSV record (eg a string) from a sequence of fields.
+ Fields containing commands (,) or double quotes (") are quotes
+ and double quotes are escaped (""). The terminating newline is
+ NOT included.
+ """
+ newFields = []
+ for field in fields:
+ assert type(field) is types.StringType
+ if field.find('"')!=-1:
+ newField = '"' + field.replace('"', '""') + '"'
+ elif field.find(',')!=-1 or field.find('\n')!=-1 or field.find('\r')!=-1:
+ newField = '"' + field + '"'
+ else:
+ newField = field
+ newFields.append(newField)
+ return ','.join(newFields)
diff --git a/paste/webkit/FakeWebware/MiscUtils/CSVParser.py b/paste/webkit/FakeWebware/MiscUtils/CSVParser.py
new file mode 100644
index 0000000..047b3b9
--- /dev/null
+++ b/paste/webkit/FakeWebware/MiscUtils/CSVParser.py
@@ -0,0 +1,265 @@
+# The states of the parser
+StartRecord = 0
+StartField = 1
+InField = 2
+QuoteInField = 3
+InQuotedField = 4
+QuoteInQuotedField = 5
+EndQuotedField = 6
+
+# State handlers can return Finished to terminate parsing early
+Finished = 10
+
+
+class ParseError(Exception):
+ pass
+
+
+class CSVParser:
+ """
+ Parses CSV files including all subtleties such as:
+ * commas in fields
+ * double quotes in fields
+ * embedded newlines in fields
+ - Examples of programs that produce such beasts include
+ MySQL and Excel
+
+ For a higher-level, friendlier CSV class with many conveniences,
+ see DataTable (which uses this class for its parsing).
+
+ Example:
+ records = []
+ parse = CSVParser().parse
+ for line in lines:
+ results = parse(line)
+ if results is not None:
+ records.append(results)
+
+ CREDIT
+
+ The algorithm was taken directly from the open source Python
+ C-extension, csv:
+ http://www.object-craft.com.au/projects/csv/
+
+ It would be nice to use the csv module when present, since it is
+ substantially faster. Before that can be done, it needs to support
+ allowComments and stripWhitespace, and pass the TestCSVParser.py
+ test suite.
+ """
+
+ def __init__(self, allowComments=1, stripWhitespace=1, fieldSep=',', autoReset=1, doubleQuote=1):
+ """
+ @@ document
+ """
+ # settings
+ self._allowComments = allowComments
+ self._stripWhitespace = stripWhitespace
+ self._doubleQuote = doubleQuote
+ self._fieldSep = fieldSep
+ self._autoReset = autoReset
+
+ # Other
+ self._state = StartRecord
+ self._fields = []
+ self._hadParseError = 0
+ self._field = [] # a list of chars for the cur field
+ self.addChar = self._field.append
+
+ # The handlers for the various states
+ self._handlers = [
+ self.startRecord,
+ self.startField,
+ self.inField,
+ self.quoteInField,
+ self.inQuotedField,
+ self.quoteInQuotedField,
+ self.endQuotedField,
+ ]
+
+
+ ## Parse ##
+
+ def parse(self, line):
+ """
+ Parse the single line and return a list or string fields, or
+ None if the CSV record contains embedded newlines and the
+ record is not yet complete.
+ """
+ if self._autoReset and self._hadParseError:
+ self.reset()
+ handlers = self._handlers
+
+ i = 0
+ lineLen = len(line)
+ while i<lineLen:
+ c = line[i]
+ if c=='\r':
+ i += 1
+ if i==lineLen:
+ break # mac end of line
+ c = line[i]
+ if c=='\n':
+ i += 1
+ if i==lineLen:
+ break # dos end of line
+
+ self._hadParseError = 1
+ raise ParseError('Newline inside string')
+
+ elif c=='\n':
+ i += 1
+ if i==lineLen:
+ break # unix end of line
+
+ self._hadParseError = 1
+ raise ParseError('Newline inside string')
+
+ else:
+ if handlers[self._state](c)==Finished: # process a character
+ break
+
+ i += 1
+
+ handlers[self._state]('\0') # signal the end of the input
+
+ if self._state==StartRecord:
+ fields = self._fields
+ self._fields = []
+ if self._stripWhitespace:
+ fields = [field.strip() for field in fields]
+ return fields
+ else:
+ return None # indicates multi-line record; eg not finished
+
+
+ ## Reset ##
+
+ def reset(self):
+ """
+ Resets the parser to a fresh state in order to recover from
+ exceptions. But if autoReset is true (the default), this is
+ done automatically.
+ """
+ self._fields = []
+ self._state = StartRecord
+ self.hasParseError = 0
+
+
+ ## State Handlers ##
+
+ def startRecord(self, c):
+ if c!='\0': # not empty line
+ if c=='#' and self._allowComments:
+ return Finished
+ else:
+ self._state = StartField
+ self.startField(c)
+
+ def startField(self, c):
+ if c=='"':
+ self._state = InQuotedField # start quoted field
+ elif c==self._fieldSep:
+ self.saveField() # save empty field
+ elif c==' ' and self._stripWhitespace:
+ pass # skip over preceding whitespace
+ elif c=='\0':
+ self.saveField() # save empty field
+ self._state = StartRecord
+ else:
+ self.addChar(c) # begin new unquoted field
+ self._state = InField
+
+ def inField(self, c):
+ # in unquoted field
+ if c==self._fieldSep:
+ self.saveField()
+ self._state = StartField
+ elif c=='\0':
+ self.saveField() # end of line
+ self._state = StartRecord
+ elif c=='"' and self._doubleQuote:
+ self._state = QuoteInField
+ else:
+ self.addChar(c) # normal character
+
+ def quoteInField(self, c):
+ self.addChar('"')
+ if c=='"':
+ self._state = InField # save "" as "
+ elif c=='\0':
+ self.saveField() # end of line
+ self._state = StartRecord
+ elif c==self._fieldSep:
+ self.saveField()
+ self._state = StartField
+ else:
+ self.addChar(c) # normal character
+ self._state = InField
+
+ def inQuotedField(self, c):
+ if c=='"':
+ if self._doubleQuote:
+ self._state = QuoteInQuotedField
+ else:
+ self.saveField() # end of field
+ self._state = EndQuotedField
+ elif c=='\0':
+ self.addChar('\n') # end of line
+ else:
+ self.addChar(c) # normal character
+
+ def quoteInQuotedField(self, c):
+ if c=='"':
+ self.addChar('"') # save "" as "
+ self._state = InQuotedField
+ elif c==self._fieldSep:
+ self.saveField()
+ self._state = StartField
+ elif c==' ' and self._stripWhitespace:
+ pass # skip it
+ elif c=='\0':
+ self.saveField() # end of line
+ self._state = StartRecord
+ else:
+ self._hadParseError = 1 # illegal
+ raise ParseError, '%s expected after "' % self._fieldSep
+
+ def endQuotedField(self, c):
+ if c==self._fieldSep: # seen closing " on quoted field
+ self._state = StartField # wait for new field
+ elif c=='\0':
+ self._state = StartRecord # end of line
+ else:
+ self._hadParseError = 1
+ raise ParseError, '%s expected after "' % self._fieldSep
+
+ def saveField(self):
+ self._fields.append(''.join(self._field))
+ self._field = []
+ self.addChar = self._field.append
+
+
+# Call the global function parse() if you like the default settings of the CSVParser
+_parser = CSVParser()
+parse = _parser.parse
+
+
+import types
+def joinCSVFields(fields):
+ """
+ Returns a CSV record (eg a string) from a sequence of fields.
+ Fields containing commands (,) or double quotes (") are quoted
+ and double quotes are escaped (""). The terminating newline is
+ NOT included.
+ """
+ newFields = []
+ for field in fields:
+ assert type(field) is types.StringType
+ if field.find('"')!=-1:
+ newField = '"' + field.replace('"', '""') + '"'
+ elif field.find(',')!=-1:
+ newField = '"' + field + '"'
+ else:
+ newField = field
+ newFields.append(newField)
+ return ','.join(newFields)
diff --git a/paste/webkit/FakeWebware/MiscUtils/DBPool.py b/paste/webkit/FakeWebware/MiscUtils/DBPool.py
new file mode 100644
index 0000000..522f268
--- /dev/null
+++ b/paste/webkit/FakeWebware/MiscUtils/DBPool.py
@@ -0,0 +1,128 @@
+"""
+DBPool.py
+
+Implements a pool of cached connections to a database. This should result in
+a speedup for persistent apps. The pool of connections is threadsafe
+regardless of whether the DB API module question in general has a
+threadsafety of 1 or 2.
+
+For more information on the DB API, see:
+ http://www.python.org/topics/database/DatabaseAPI-2.0.html
+
+The idea behind DBPool is that it's completely seamless, so once you have
+established your connection, use it just as you would any other DB-API
+compliant module. For example:
+
+ dbPool = DBPool(MySQLdb, 5, host=xxx, user=xxx, ...)
+ db = dbPool.getConnection()
+
+Now use "db" exactly as if it were a MySQLdb connection. It's really
+just a proxy class.
+
+db.close() will return the connection to the pool, not actually
+close it. This is so your existing code works nicely.
+
+
+FUTURE
+
+* If in the presence of WebKit, register ourselves as a Can.
+
+
+CREDIT
+
+* Contributed by Dan Green
+* thread safety bug found by Tom Schwaller
+* Fixes by Geoff Talvola (thread safety in _threadsafe_getConnection()).
+* Clean up by Chuck Esterbrook.
+* Fix unthreadsafe functions which were leaking, Jay Love
+* Eli Green's webware-discuss comments were lifted for additional docs.
+"""
+
+
+import threading
+
+
+class DBPoolError(Exception): pass
+class UnsupportedError(DBPoolError): pass
+
+
+class PooledConnection:
+ """ A wrapper for database connections to help with DBPool. You don't normally deal with this class directly, but use DBPool to get new connections. """
+
+ def __init__(self, pool, con):
+ self._con = con
+ self._pool = pool
+
+ def close(self):
+ if self._con is not None:
+ self._pool.returnConnection(self)
+ self._con = None
+
+ def __getattr__(self, name):
+ return getattr(self._con, name)
+
+ def __del__(self):
+ self.close()
+
+
+class DBPool:
+
+ def __init__(self, dbModule, maxConnections, *args, **kwargs):
+ if dbModule.threadsafety==0:
+ raise UnsupportedError, "Database module does not support any level of threading."
+ elif dbModule.threadsafety==1:
+ from Queue import Queue
+ self._queue = Queue(maxConnections)
+ self.addConnection = self._unthreadsafe_addConnection
+ self.getConnection = self._unthreadsafe_getConnection
+ self.returnConnection = self._unthreadsafe_returnConnection
+ elif dbModule.threadsafety>=2:
+ self._lock = threading.Lock()
+ self._nextCon = 0
+ self._connections = []
+ self.addConnection = self._threadsafe_addConnection
+ self.getConnection = self._threadsafe_getConnection
+ self.returnConnection = self._threadsafe_returnConnection
+
+ # @@ 2000-12-04 ce: Should we really make all the connections now?
+ # Couldn't we do this on demand?
+ for i in range(maxConnections):
+ con = apply(dbModule.connect, args, kwargs)
+ self.addConnection(con)
+
+
+ # threadsafe/unthreadsafe refers to the database _module_, not THIS class..
+ # this class is definitely threadsafe (um. that is, I hope so - Dan)
+
+ def _threadsafe_addConnection(self, con):
+ self._connections.append(con)
+
+
+ def _threadsafe_getConnection(self):
+ self._lock.acquire()
+ try:
+ con = PooledConnection(self, self._connections[self._nextCon])
+ self._nextCon = self._nextCon + 1
+ if self._nextCon >= len(self._connections):
+ self._nextCon = 0
+ return con
+ finally:
+ self._lock.release()
+
+ def _threadsafe_returnConnection(self, con):
+ return
+
+ # These functions are used with DB modules that have connection level threadsafety, like PostgreSQL.
+ #
+ def _unthreadsafe_addConnection(self, con):
+ self._queue.put(con)
+
+ def _unthreadsafe_getConnection(self):
+ return PooledConnection(self, self._queue.get())
+
+ def _unthreadsafe_returnConnection(self, conpool):
+ """
+ This should never be called explicitly outside of this module.
+ """
+ self.addConnection(conpool._con)
+
diff --git a/paste/webkit/FakeWebware/MiscUtils/DataTable.py b/paste/webkit/FakeWebware/MiscUtils/DataTable.py
new file mode 100644
index 0000000..1c92522
--- /dev/null
+++ b/paste/webkit/FakeWebware/MiscUtils/DataTable.py
@@ -0,0 +1,804 @@
+"""
+DataTable.py
+
+
+INTRODUCTION
+
+This class is useful for representing a table of data arranged by named
+columns, where each row in the table can be thought of as a record:
+
+ name phoneNumber
+ ------ -----------
+ Chuck 893-3498
+ Bill 893-0439
+ John 893-5901
+
+This data often comes from delimited text files which typically
+have well defined columns or fields with several rows each of which can
+be thought of as a record.
+
+Using a DataTable can be as easy as using lists and dictionaries:
+
+ table = DataTable('users.csv')
+ for row in table:
+ print row['name'], row['phoneNumber']
+
+Or even:
+
+ table = DataTable('users.csv')
+ for row in table:
+ print '%(name)s %(phoneNumber)s' % row
+
+The above print statement relies on the fact that rows can be treated
+like dictionaries, using the column headings as keys.
+
+You can also treat a row like an array:
+
+ table = DataTable('something.tabbed', delimiter='\t')
+ for row in table:
+ for item in row:
+ print item,
+ print
+
+
+COLUMNS
+
+Column headings can have a type specification like so:
+ name, age:int, zip:int
+
+Possible types include string, int, float and datetime. However,
+datetime is not well supported right now.
+
+String is assumed if no type is specified but you can set that
+assumption when you create the table:
+
+ table = DataTable(headings, defaultType='float')
+
+Using types like int and float will cause DataTable to actually
+convert the string values (perhaps read from a file) to these types
+so that you can use them in natural operations. For example:
+
+ if row['age']>120:
+ self.flagData(row, 'age looks high')
+
+As you can see, each row can be accessed as a dictionary with keys
+according the column headings. Names are case sensitive.
+
+
+ADDING ROWS
+
+Like Python lists, data tables have an append() method. You can append
+TableRecords, or you pass a dictionary, list or object, in which case a
+TableRecord is created based on given values. See the method docs below
+for more details.
+
+
+FILES
+
+By default, the files that DataTable reads from are expected to be
+comma-separated value files.
+
+Limited comments are supported: A comment is any line whose very first
+character is a #. This allows you to easily comment out lines in your
+data files without having to remove them.
+
+Whitespace around field values is stripped.
+
+You can control all this behavior through the arguments found in the
+initializer and the various readFoo() methods:
+
+ ...delimiter=',', allowComments=1, stripWhite=1
+
+For example:
+
+ table = DataTable('foo.tabbed', delimiter='\t', allowComments=0, stripWhite=0)
+
+You should access these parameters by their name since additional ones
+could appear in the future, thereby changing the order.
+
+If you are creating these text files, we recommend the
+comma-separated-value format, or CSV. This format is better defined
+than the tab delimited format, and can easily be edited and manipulated
+by popular spreadsheets and databases.
+
+
+MICROSOFT EXCEL
+
+On Microsoft Windows systems with Excel and the win32all package
+(http://starship.python.net/crew/mhammond/), DataTable will use Excel
+(via COM) to read ".xls" files.
+
+from MiscUtils import DataTable
+assert DataTable.canReadExcel()
+table = DataTable.DataTable('foo.xls')
+
+With consistency to its CSV processing, DataTable will ignore any row
+whose first cell is '#' and strip surrounding whitespace around strings.
+
+
+TABLES FROM SCRATCH
+
+Here's an example that constructs a table from scratch:
+
+ table = DataTable(['name', 'age:int'])
+ table.append(['John', 80])
+ table.append({'name': 'John', 'age': 80})
+ print table
+
+
+QUERIES
+
+A simple query mechanism is supported for equality of fields:
+
+ matches = table.recordsEqualTo({'uid': 5})
+ if matches:
+ for match in matches:
+ print match
+ else:
+ print 'No matches.'
+
+
+COMMON USES
+
+* Programs can keep configuration and other data in simple comma-
+separated text files and use DataTable to access them. For example, a
+web site could read it's sidebar links from such a file, thereby
+allowing people who don't know Python (or even HTML) to edit these
+links without having to understand other implementation parts of the
+site.
+
+* Servers can use DataTable to read and write log files.
+
+
+FROM THE COMMAND LINE
+
+The only purpose in invoking DataTable from the command line is to see
+if it will read a file:
+
+> python DataTable.py foo.csv
+
+The data table is printed to stdout.
+
+
+CACHING
+
+DataTable uses "pickle caching" so that it can read .csv files faster
+on subsequent loads. You can disable this across the board with:
+ from MiscUtils.DataTable import DataTable
+ DataTable.usePickleCache = 0
+
+Or per instance by passing "usePickleCache=0" to the constructor.
+
+See the docstring of PickleCache.py for more information.
+
+
+MORE DOCS
+
+Some of the methods in this module have worthwhile doc strings to look
+at. See below.
+
+
+TO DO
+
+* Allow callback parameter or setting for parsing CSV records.
+* Perhaps TableRecord should inherit UserList and UserDict and override methods as appropriate...?
+* Better support for datetime.
+* _types and BlankValues aren't really packaged, advertised or
+ documented for customization by the user of this module.
+* DataTable:
+ * Parameterize the TextColumn class.
+ * Parameterize the TableRecord class.
+ * More list-like methods such as insert()
+ * writeFileNamed() is flawed: it doesn't write the table column
+ type
+ * Should it inherit from UserList?
+* Add error checking that a column name is not a number (which could
+ cause problems).
+* Look for various @@ tags through out the code.
+
+"""
+
+
+import os, string, sys
+from CSVParser import CSVParser
+from string import join, replace, split, strip
+from types import *
+
+try:
+ StringTypes
+except NameError:
+ StringTypes = StringType
+
+try:
+ from MiscUtils import NoDefault
+except ImportError:
+ class NoDefault:
+ pass
+
+try:
+ from mx.DateTime import DateTimeType, DateTimeFrom
+except ImportError:
+ pass
+
+
+## Types ##
+
+DateTimeType = "<custom-type 'datetime'>"
+ObjectType = "<type 'Object'>"
+
+_types = {
+ 'string': StringType,
+ 'int': IntType,
+ 'long': LongType,
+ 'float': FloatType,
+ 'datetime': DateTimeType,
+ 'object': ObjectType,
+}
+
+
+## Functions ##
+
+def canReadExcel():
+ try:
+ from win32com.client import Dispatch
+ Dispatch("Excel.Application")
+ except:
+ return False
+ else:
+ return True
+
+
+## Classes ##
+
+
+class DataTableError(Exception):
+ pass
+
+
+class TableColumn:
+ """
+ A table column represents a column of the table including name and
+ type.
+
+ It does not contain the actual values of the column. These are
+ stored individually in the rows.
+ """
+
+ def __init__(self, spec):
+
+ # spec is a string such as 'name' or 'name:type'
+ fields = split(spec, ':')
+ if len(fields)>2:
+ raise DataTableError, 'Invalid column spec %s' % repr(spec)
+ self._name = fields[0]
+
+ if len(fields)==1:
+ self._type = None
+ else:
+ self.setType(fields[1])
+
+ def name(self):
+ return self._name
+
+ def type(self):
+ return self._type
+
+ def setType(self, type):
+ """ Sets the type (by a string containing the name) of the heading. Usually invoked by DataTable to set the default type for columns whose types were not specified. """
+ if type==None:
+ self._type = None
+ else:
+ try:
+ self._type = _types[type]
+ except:
+ raise DataTableError, 'Unknown type %s' % repr(type)
+
+ def __repr__(self):
+ return '<%s %s with %s at %x>' % (
+ self.__class__.__name__, repr(self._name), repr(self._type), id(self))
+
+ def __str__(self):
+ return self._name
+
+
+ ## Utilities ##
+
+ def valueForRawValue(self, rawValue):
+ """ The rawValue is typically a string or value already of the appropriate type. TableRecord invokes this method to ensure that values (especially strings that come from files) are the correct types (e.g., ints are ints and floats are floats). """
+ # @@ 2000-07-23 ce: an if-else ladder? perhaps these should be dispatched messages or a class hier
+ if self._type is StringType:
+ value = str(rawValue)
+ elif self._type is IntType:
+ if rawValue=='':
+ value = 0
+ else:
+ value = int(rawValue)
+ elif self._type is LongType:
+ if rawValue=='':
+ value = 0
+ else:
+ value = long(rawValue)
+ elif self._type is FloatType:
+ if rawValue=='':
+ value = 0.0
+ else:
+ value = float(rawValue)
+ elif self._type is DateTimeType:
+ value = DateTimeFrom(rawValue)
+ elif self._type is ObjectType:
+ value = rawValue
+ else:
+ # no type set, leave values as they are
+ value = rawValue
+ return value
+
+
+class DataTable:
+ """
+ See the doc string for this module.
+ """
+
+ usePickleCache = 1
+
+
+ ## Init ##
+
+ def __init__(self, filenameOrHeadings=None, delimiter=',', allowComments=1, stripWhite=1, defaultType=None, usePickleCache=None):
+ if usePickleCache is None:
+ self.usePickleCache = self.usePickleCache # grab the class-level attr
+ else:
+ self.usePickleCache = usePickleCache
+ if defaultType and not _types.has_key(defaultType):
+ raise DataTableError, 'Unknown type for default type: %s' % repr(defaultType)
+ self._defaultType = defaultType
+ self._filename = None
+ self._headings = []
+ self._rows = []
+ if filenameOrHeadings:
+ if type(filenameOrHeadings) is StringType:
+ self.readFileNamed(filenameOrHeadings, delimiter, allowComments, stripWhite)
+ else:
+ self.setHeadings(filenameOrHeadings)
+
+
+ ## File I/O ##
+
+ def readFileNamed(self, filename, delimiter=',', allowComments=1, stripWhite=1):
+ self._filename = filename
+ data = None
+ if self.usePickleCache:
+ from PickleCache import readPickleCache, writePickleCache
+ data = readPickleCache(filename, pickleVersion=1, source='MiscUtils.DataTable')
+ if data is None:
+ if self._filename.endswith('.xls'):
+ self.readExcel()
+ else:
+ file = open(self._filename, 'r')
+ self.readFile(file, delimiter, allowComments, stripWhite)
+ file.close()
+ if self.usePickleCache:
+ writePickleCache(self, filename, pickleVersion=1, source='MiscUtils.DataTable')
+ else:
+ self.__dict__ = data.__dict__
+ return self
+
+ def readFile(self, file, delimiter=',', allowComments=1, stripWhite=1):
+ return self.readLines(file.readlines(), delimiter, allowComments, stripWhite)
+
+ def readString(self, string, delimiter=',', allowComments=1, stripWhite=1):
+ return self.readLines(split(string, '\n'), delimiter, allowComments, stripWhite)
+
+ def readLines(self, lines, delimiter=',', allowComments=1, stripWhite=1):
+ if self._defaultType is None:
+ self._defaultType = 'string'
+ haveReadHeadings = 0
+ parse = CSVParser(fieldSep=delimiter, allowComments=allowComments, stripWhitespace=stripWhite).parse
+ values = ''
+ for line in lines:
+ # process a row, either headings or data
+ values = parse(line)
+ if values:
+ if haveReadHeadings:
+ row = TableRecord(self, values)
+ self._rows.append(row)
+ else:
+ self.setHeadings(values)
+ haveReadHeadings = 1
+ if values is None:
+ raise DataTableError, "Unfinished multiline record."
+ return self
+
+ def canReadExcel(self):
+ return canReadExcel()
+
+ def readExcel(self):
+ maxBlankRows = 10
+ numRowsToReadPerCall = 20
+
+ from win32com.client import Dispatch
+ xl = Dispatch("Excel.Application")
+ wb = xl.Workbooks.Open(os.path.abspath(self._filename))
+ try:
+ sh = wb.Worksheets(1)
+ sh.Cells(1, 1)
+
+ # determine max column
+ numCols = 1
+ while 1:
+ if sh.Cells(1, numCols).Value in [None, '']:
+ numCols -= 1
+ break
+ numCols += 1
+ if numCols<=0:
+ return
+
+ def strip(x):
+ try:
+ return x.strip()
+ except:
+ return x
+
+ # read rows of data
+ maxCol = chr(ord('A') + numCols - 1)
+ haveReadHeadings = 0
+ rowNum = 1
+ numBlankRows = 0
+ valuesBuffer = {} # keyed by row number
+ while 1:
+ try:
+ # grab a single row
+ values = valuesBuffer[rowNum]
+ except KeyError:
+ # woops. read buffer is out of fresh rows
+ valuesRows = sh.Range('A%i:%s%i' % (rowNum, maxCol, rowNum+numRowsToReadPerCall-1)).Value
+ valuesBuffer.clear()
+ j = rowNum
+ for valuesRow in valuesRows:
+ valuesBuffer[j] = valuesRow
+ j += 1
+ values = valuesBuffer[rowNum]
+
+ # non-"buffered" version, one row at a time:
+ # values = sh.Range('A%i:%s%i' % (rowNum, maxCol, rowNum)).Value[0]
+
+ values = [strip(v) for v in values]
+ nonEmpty = [v for v in values if v]
+ if nonEmpty:
+ if values[0] not in ('#', u'#'):
+ if haveReadHeadings:
+ row = TableRecord(self, values)
+ self._rows.append(row)
+ else:
+ self.setHeadings(values)
+ haveReadHeadings = 1
+ numBlankRows = 0
+ else:
+ numBlankRows += 1
+ if numBlankRows>maxBlankRows:
+ # consider end of spreadsheet
+ break
+ rowNum += 1
+ finally:
+ wb.Close()
+
+ def save(self):
+ self.writeFileNamed(self._filename)
+
+ def writeFileNamed(self, filename):
+ file = open(filename, 'w')
+ self.writeFile(file)
+ file.close()
+
+ def writeFile(self, file):
+ """
+ @@ 2000-07-20 ce: This doesn't write the column types (like :int) back out.
+ @@ 2000-07-21 ce: It's notable that a blank numeric value gets read as zero and written out that way. Also, values None are written as blanks.
+ """
+
+ # write headings
+ file.write(join(map(lambda h: str(h), self._headings), ','))
+ file.write('\n')
+
+ def ValueWritingMapper(item):
+ # So that None gets written as a blank and everything else as a string
+ if item is None:
+ return ''
+ else:
+ return str(item)
+
+ # write rows
+ for row in self._rows:
+ file.write(join(map(ValueWritingMapper, row), ','))
+ file.write('\n')
+
+ def commit(self):
+ if self._changed:
+ self.save()
+ self._changed = 0
+
+
+ ## Headings ##
+
+ def heading(self, index):
+ if type(key) is StringType:
+ key = self._nameToIndexMap[key]
+ return self._headings[index]
+
+ def hasHeading(self, name):
+ return self._nameToIndexMap.has_key(name)
+
+ def numHeadings(self):
+ return len(self._headings)
+
+ def headings(self):
+ return self._headings
+
+ def setHeadings(self, headings):
+ """ Headings can be a list of strings (like ['name', 'age:int']) or a list of TableColumns or None. """
+ if not headings:
+ self._headings = []
+ elif isinstance(headings[0], StringTypes):
+ self._headings = map(lambda h: TableColumn(h), headings)
+ elif isinstance(headings[0], TableColumn):
+ self._headings = list(headings)
+ for heading in self._headings:
+ if heading.type() is None:
+ heading.setType(self._defaultType)
+ self.createNameToIndexMap()
+
+
+ ## Row access (list like) ##
+
+ def __len__(self):
+ return len(self._rows)
+
+ def __getitem__(self, index):
+ return self._rows[index]
+
+ def append(self, object):
+ """ If object is not a TableRecord, then one is created, passing the object to initialize the TableRecord. Therefore, object can be a TableRecord, list, dictionary or object. See TableRecord for details. """
+
+ if not isinstance(object, TableRecord):
+ object = TableRecord(self, object)
+ self._rows.append(object)
+ self._changed = 1
+
+
+ ## Queries ##
+
+ def recordsEqualTo(self, dict):
+ records = []
+ keys = dict.keys()
+ for record in self._rows:
+ matches = 1
+ for key in keys:
+ if record[key]!=dict[key]:
+ matches = 0
+ break
+ if matches:
+ records.append(record)
+ return records
+
+
+ ## As a string ##
+
+ def __repr__(self):
+ # Initial info
+ s = ['DataTable: %s\n%d rows\n' % (self._filename, len(self._rows))]
+
+ # Headings
+ s.append(' ')
+ s.append(join(map(lambda h: str(h), self._headings), ', '))
+ s.append('\n')
+
+ # Records
+ i = 0
+ for row in self._rows:
+ s.append('%3d. ' % i)
+ s.append(join(map(lambda r: str(r), row), ', '))
+ s.append('\n')
+ i = i + 1
+ return join(s, '')
+
+
+ ## As a dictionary ##
+
+ def dictKeyedBy(self, key):
+ """ Returns a dictionary containing the contents of the table indexed by the particular key. This is useful for tables that have a column which represents a unique key (such as a name, serial number, etc.). """
+ dict = {}
+ for row in self:
+ dict[row[key]] = row
+ return dict
+
+
+ ## Misc access ##
+
+ def filename(self):
+ return self._filename
+
+ def nameToIndexMap(self):
+ """ Table rows keep a reference to this map in order to speed up index-by-names (as in row['name']). """
+ return self._nameToIndexMap
+
+
+ ## Self utilities ##
+
+ def createNameToIndexMap(self):
+ """
+ Invoked by self to create the nameToIndexMap after the table's
+ headings have been read/initialized.
+ """
+ map = {}
+ for i in range(len(self._headings)):
+ map[self._headings[i].name()] = i
+ self._nameToIndexMap = map
+
+
+# @@ 2000-07-20 ce: perhaps for each type we could specify a function to convert from string values to the values of the type
+
+BlankValues = {
+ StringType: '',
+ IntType: 0,
+ FloatType: 0.0,
+ DateTimeType: '',
+ None: None,
+}
+
+
+class TableRecord:
+
+ ## Init ##
+
+ def __init__(self, table, values=None):
+ """
+ Dispatches control to one of the other init methods based on the type of values. Values can be one of three things:
+ 1. A TableRecord
+ 2. A list
+ 3. A dictionary
+ 4. Any object responding to hasValueForKey() and valueForKey().
+ """
+ self._headings = table.headings()
+ self._nameToIndexMap = table.nameToIndexMap()
+ # @@ 2000-07-20 ce: Take out the headings arg to the init method since we have an attribute for that
+
+ if values is not None:
+ valuesType = type(values)
+ if valuesType is ListType or valuesType is TupleType:
+ # @@ 2000-07-20 ce: check for required attributes instead
+ self.initFromSequence(values)
+ elif valuesType is DictType:
+ self.initFromDict(values)
+ elif valuesType is InstanceType:
+ self.initFromObject(value)
+ else:
+ raise DataTableError, 'Unknown type for values %s.' % valuesType
+
+ def initFromSequence(self, values):
+ if len(self._headings)<len(values):
+ raise DataTableError, ('There are more values than headings.\nheadings(%d, %s)\nvalues(%d, %s)' % (len(self._headings), self._headings, len(values), values))
+ self._values = []
+ numHeadings = len(self._headings)
+ numValues = len(values)
+ assert numValues<=numHeadings
+ for i in range(numHeadings):
+ heading = self._headings[i]
+ if i>=numValues:
+ self._values.append(BlankValues[heading.type()])
+ else:
+ self._values.append(heading.valueForRawValue(values[i]))
+
+ def initFromDict(self, dict):
+ self._values = []
+ for heading in self._headings:
+ name = heading.name()
+ if dict.has_key(name):
+ self._values.append(heading.valueForRawValue(dict[name]))
+ else:
+ self._values.append(BlankValues[heading.type()])
+
+ def initFromObject(self, object):
+ """
+ The object is expected to response to hasValueForKey(name) and
+ valueForKey(name) for each of the headings in the table. It's
+ alright if the object returns 0 for hasValueForKey(). In that
+ case, a "blank" value is assumed (such as zero or an empty
+ string). If hasValueForKey() returns 1, then valueForKey() must
+ return a value.
+ """
+ self._values = []
+ for heading in self._headings:
+ name = heading.name()
+ if object.hasValueForKey(name):
+ self._values.append(heading.valueForRawValue(object.valueForKey(name)))
+ else:
+ self._values.append(BlankValues[heading.type()])
+
+
+ ## Accessing like a sequence or dictionary ##
+
+ def __len__(self):
+ return len(self._values)
+
+ def __getitem__(self, key):
+ if isinstance(key, StringTypes):
+ key = self._nameToIndexMap[key]
+ try:
+ return self._values[key]
+ except TypeError:
+ raise TypeError, 'key=%r, key type=%r, self._values=%r' % (key, type(key), self._values)
+
+ def __setitem__(self, key, value):
+ if type(key) is StringType:
+ key = self._nameToIndexMap[key]
+ self._values[key] = value
+
+ def __repr__(self):
+ return '%s' % self._values
+
+ def get(self, key, default=None):
+ index = self._nameToIndexMap.get(key, None)
+ if index is None:
+ return default
+ else:
+ return self._values[index]
+
+ def has_key(self, key):
+ return self._nameToIndexMap.has_key(key)
+
+ def keys(self):
+ return self._nameToIndexMap.keys()
+
+ def values(self):
+ return self._values
+
+ def items(self):
+ items = []
+ for key in self.keys():
+ items.append((key, self[key]))
+ return items
+
+
+ ## Additional access ##
+
+ def asList(self):
+ """
+ Returns a sequence whose values are the same at the record's
+ and in the order defined by the table.
+ """
+ # It just so happens that our implementation already has this
+ return self._values[:]
+
+ def asDict(self):
+ """ Returns a dictionary whose key-values match the table record. """
+ dict = {}
+ nameToIndexMap = self._nameToIndexMap
+ for key in nameToIndexMap.keys():
+ dict[key] = self._values[nameToIndexMap[key]]
+ return dict
+
+
+ ## valueForFoo() family ##
+
+ def valueForKey(self, key, default=NoDefault):
+ if default is NoDefault:
+ return self[key]
+ else:
+ return self.get(key, default)
+
+ def valueForAttr(self, attr, default=NoDefault):
+ return self.valueForKey(attr['Name'], default)
+
+
+
+def main(args=None):
+ if args is None:
+ args = sys.argv
+ for arg in args[1:]:
+ dt = DataTable(arg)
+ print '*** %s ***' % arg
+ print dt
+ print
+
+
+if __name__=='__main__':
+ main()
diff --git a/paste/webkit/FakeWebware/MiscUtils/DateInterval.py b/paste/webkit/FakeWebware/MiscUtils/DateInterval.py
new file mode 100644
index 0000000..9081ef1
--- /dev/null
+++ b/paste/webkit/FakeWebware/MiscUtils/DateInterval.py
@@ -0,0 +1,70 @@
+"""
+DateInterval.py
+
+Convert interval strings (in the form of 1w2d, etc) to
+seconds, and back again. Is not exactly about months or
+years (leap years in particular).
+
+Accepts (y)ear, (b)month, (w)eek, (d)ay, (h)our, (m)inute, (s)econd.
+
+Exports only timeEncode and timeDecode functions.
+"""
+
+import re
+
+second = 1
+minute = second*60
+hour = minute*60
+day = hour*24
+week = day*7
+month = day*30
+year = day*365
+timeValues = {
+ 'y': year,
+ 'b': month,
+ 'w': week,
+ 'd': day,
+ 'h': hour,
+ 'm': minute,
+ 's': second,
+ }
+timeOrdered = timeValues.items()
+timeOrdered.sort(lambda a, b: -cmp(a[1], b[1]))
+
+def timeEncode(seconds):
+ """Encodes a number of seconds (representing a time interval)
+ into a form like 1h2d3s."""
+ s = ''
+ for char, amount in timeOrdered:
+ if seconds >= amount:
+ i, seconds = divmod(seconds, amount)
+ s += '%i%s' % (i, char)
+ return s
+
+_timeRE = re.compile(r'[0-9]+[a-zA-Z]')
+def timeDecode(s):
+ """Decodes a number in the format 1h4d3m (1 hour, 3 days, 3 minutes)
+ into a number of seconds"""
+ time = 0
+ for match in allMatches(s, _timeRE):
+ char = match.group(0)[-1].lower()
+ if not timeValues.has_key(char):
+ # @@: should signal error
+ continue
+ time += int(match.group(0)[:-1]) * timeValues[char]
+ return time
+
+# @@-sgd 2002-12-23 - this function does not belong in this module, find a better place.
+def allMatches(source, regex):
+ """Return a list of matches for regex in source
+ """
+ pos = 0
+ end = len(source)
+ rv = []
+ match = regex.search(source, pos)
+ while match:
+ rv.append(match)
+ match = regex.search(source, match.end() )
+ return rv
+
+__all__ = [timeEncode, timeDecode]
diff --git a/paste/webkit/FakeWebware/MiscUtils/DictForArgs.py b/paste/webkit/FakeWebware/MiscUtils/DictForArgs.py
new file mode 100644
index 0000000..85cc545
--- /dev/null
+++ b/paste/webkit/FakeWebware/MiscUtils/DictForArgs.py
@@ -0,0 +1,209 @@
+"""
+DictForArgs.py
+
+
+See the doc string for the DictForArgs() function.
+
+Also, there is a test suite in Testing/TestDictForArgs.py
+"""
+
+
+import re, string
+
+
+class DictForArgsError(Exception):
+ pass
+
+def _SyntaxError(s):
+ raise DictForArgsError, 'Syntax error: %s' % repr(s)
+
+def DictForArgs(s):
+ """
+ Takes an input such as:
+ x=3
+ name="foo"
+ first='john' last='doe'
+ required border=3
+
+ And returns a dictionary representing the same. For keys that aren't
+ given an explicit value (such as 'required' above), the value is '1'.
+
+ All values are interpreted as strings. If you want ints and floats,
+ you'll have to convert them yourself.
+
+ This syntax is equivalent to what you find in HTML and close to other
+ ML languages such as XML.
+
+ Returns {} for an empty string.
+
+ The informal grammar is:
+ (NAME [=NAME|STRING])*
+
+ Will raise DictForArgsError if the string is invalid.
+
+ See also: PyDictForArgs() and ExpandDictWithExtras() in this module
+ """
+
+ s = string.strip(s)
+
+ # Tokenize
+
+ # @@ 2001-09-29 ce: push these outside for better performance
+ nameRE = re.compile(r'\w+')
+ equalsRE = re.compile(r'\=')
+ stringRE = re.compile(r'''
+ "[^"]+"|
+ '[^']+'|
+ \S+''', re.VERBOSE) #'
+ whiteRE = re.compile(r'\s+')
+ REs = [nameRE, equalsRE, stringRE, whiteRE]
+
+ verbose = 0
+ matches = []
+ start = 0
+ sLen = len(s)
+
+ if verbose:
+ print '>> DictForArgs(%s)' % repr(s)
+ print '>> sLen:', sLen
+ while start<sLen:
+ for regEx in REs:
+ if verbose: print '>> try:', regEx
+ match = regEx.match(s, start)
+ if verbose: print '>> match:', match
+ if match is not None:
+ if match.re is not whiteRE:
+ matches.append(match)
+ start = match.end()
+ if verbose: print '>> new start:', start
+ break
+ else:
+ _SyntaxError(s)
+
+ if verbose:
+ names = []
+ for match in matches:
+ if match.re is nameRE:
+ name = 'name'
+ elif match.re is equalsRE:
+ name = 'equals'
+ elif match.re is stringRE:
+ name = 'string'
+ elif match.re is whiteRE:
+ name = 'white'
+ names.append(name)
+ #print '>> match =', name, match
+ print '>> names =', names
+
+
+ # Process tokens
+
+ # At this point we have a list of all the tokens (as re.Match objects)
+ # We need to process these into a dictionary.
+
+ dict = {}
+ matchesLen = len(matches)
+ i = 0
+ while i<matchesLen:
+ match = matches[i]
+ if i+1<matchesLen:
+ peekMatch = matches[i+1]
+ else:
+ peekMatch = None
+ if match.re is nameRE:
+ if peekMatch is not None:
+ if peekMatch.re is nameRE:
+ # We have a name without an explicit value
+ dict[match.group()] = '1'
+ i = i + 1
+ continue
+ if peekMatch.re is equalsRE:
+ if i+2<matchesLen:
+ target = matches[i+2]
+ if target.re is nameRE or target.re is stringRE:
+ value = target.group()
+ if value[0]=="'" or value[0]=='"':
+ value = value[1:-1]
+ #value = "'''%s'''" % value[1:-1]
+ #value = eval(value)
+ dict[match.group()] = value
+ i = i + 3
+ continue
+ else:
+ dict[match.group()] = '1'
+ i = i + 1
+ continue
+ _SyntaxError(s)
+
+
+ if verbose: print
+
+ return dict
+
+
+from string import letters
+
+def PyDictForArgs(s):
+ """
+ Takes an input such as:
+ x=3
+ name="foo"
+ first='john'; last='doe'
+ list=[1, 2, 3]; name='foo'
+
+ And returns a dictionary representing the same.
+
+ All values are interpreted as Python expressions. Any error in these
+ expressions will raise the appropriate Python exception. This syntax
+ allows much more power than DictForArgs() since you can include
+ lists, dictionaries, actual ints and floats, etc.
+
+ This could also open the door to hacking your software if the input
+ comes from a tainted source such as an HTML form or an unprotected
+ configuration file.
+
+ Returns {} for an empty string.
+
+ See also: DictForArgs() and ExpandDictWithExtras() in this module
+ """
+ if s:
+ s = s.strip()
+ if not s:
+ return {}
+
+ # special case: just a name
+ # meaning: name=1
+ # example: isAbstract
+ if s.find(' ')==-1 and s.find('=')==-1 and s[0] in letters:
+ s += '=1'
+
+ results = {}
+ exec s in results
+
+ del results['__builtins__']
+ return results
+
+
+def ExpandDictWithExtras(dict, key='Extras', delKey=1, dictForArgs=DictForArgs):
+ """
+ Returns a dictionary with the 'Extras' column expanded by DictForArgs(). For example, given:
+ { 'Name': 'foo', 'Extras': 'x=1 y=2' }
+ The return value is:
+ { 'Name': 'foo', 'x': '1', 'y': '2' }
+ The key argument controls what key in the dictionary is used to hold the extra arguments. The delKey argument controls whether that key and its corresponding value are retained.
+ The same dictionary may be returned if there is no extras key.
+ The most typical use of this function is to pass a row from a DataTable that was initialized from a CSV file (e.g., a spreadsheet or tabular file). FormKit and MiddleKit both use CSV files and allow for an Extras column to specify attributes that occur infrequently.
+ """
+
+ if dict.has_key(key):
+ newDict = {}
+ # We use the following for loop rather than newDict.update()
+ # so that the dict arg can be dictionary-like.
+ for k, v in dict.items():
+ newDict[k] = v
+ if delKey:
+ del newDict[key]
+ newDict.update(dictForArgs(dict[key]))
+ return newDict
+ else:
+ return dict
diff --git a/paste/webkit/FakeWebware/MiscUtils/Error.py b/paste/webkit/FakeWebware/MiscUtils/Error.py
new file mode 100644
index 0000000..b26f3e1
--- /dev/null
+++ b/paste/webkit/FakeWebware/MiscUtils/Error.py
@@ -0,0 +1,50 @@
+from UserDict import UserDict
+
+
+class Error(UserDict):
+ """
+ An error is a dictionary-like object, containing a specific user-readable error message and an object associated with it. Since Error inherits UserDict, other informative values can be arbitrarily attached to errors. For this reason, subclassing Error is rare.
+
+ Example:
+ err = Error(user, 'Invalid password.')
+ err['time'] = time.time()
+ err['attempts'] = attempts
+
+ The object and message can be accessed via methods:
+ print err.object()
+ print err.message()
+
+ When creating errors, you can pass None for both the object and the message. You can also pass additional values, which are then included in the error:
+ >>> err = Error(None, 'Too bad.', timestamp=time.time())
+ >>> err.keys()
+ ['timestamp']
+
+ Or include the values as a dictionary, instead of keyword arguments:
+ >>> info = {'timestamp': time.time()}
+ >>> err = Error(None, 'Too bad.', info)
+
+ Or you could even do both if you needed to.
+ """
+
+ def __init__(self, object, message, valueDict={}, **valueArgs):
+ """ Initializes an error with the object the error occurred for, and the user-readable error message. The message should be self sufficient such that if printed by itself, the user would understand it. """
+ UserDict.__init__(self)
+ self._object = object
+ self._message = message
+ self.update(valueDict)
+ self.update(valueArgs)
+
+ def object(self):
+ return self._object
+
+ def message(self):
+ return self._message
+
+ def __repr__(self):
+ return 'ERROR(object=%s; message=%s; data=%s)' % (repr(self._object), repr(self._message), repr(self.data))
+
+ def __str__(self):
+ return 'ERROR: %s' % self._message
+
+ def __nonzero__(self):
+ return 1
diff --git a/paste/webkit/FakeWebware/MiscUtils/Funcs.py b/paste/webkit/FakeWebware/MiscUtils/Funcs.py
new file mode 100644
index 0000000..236f925
--- /dev/null
+++ b/paste/webkit/FakeWebware/MiscUtils/Funcs.py
@@ -0,0 +1,325 @@
+"""
+Funcs.py
+
+Funcs.py, a member of MiscUtils, holds functions that don't fit in anywhere else.
+"""
+
+import md5, os, random, string, time, sys, tempfile
+True, False = 1==1, 1==0
+
+
+def commas(number):
+ """ Returns the given number as a string with commas to separate the thousands positions. The number can be a float, int, long or string. Returns None for None. """
+ if number is None:
+ return None
+ if not number:
+ return str(number)
+ number = list(str(number))
+ if '.' in number:
+ i = number.index('.')
+ else:
+ i = len(number)
+ while 1:
+ i = i-3
+ if i<=0 or number[i-1]=='-':
+ break
+ number[i:i] = [',']
+ return string.join(number, '')
+
+
+def charWrap(s, width, hanging=0):
+ """ Returns a new version of the string word wrapped with the given width and hanging indent. The font is assumed to be monospaced.
+ This can be useful for including text between <pre> </pre> tags since <pre> will not word wrap and for lengthly lines, will increase the width of a web page.
+ It can also be used to help delineate the entries in log-style output by passing hanging=4.
+ """
+ import string
+ if not s:
+ return s
+ assert hanging<width
+ hanging = ' ' * hanging
+ lines = string.split(s, '\n')
+ i = 0
+ while i<len(lines):
+ s = lines[i]
+ while len(s)>width:
+ t = s[width:]
+ s = s[:width]
+ lines[i] = s
+ i = i + 1
+ lines.insert(i, None)
+ s = hanging + t
+ else:
+ lines[i] = s
+ i = i + 1
+ return string.join(lines, '\n')
+
+# Python 2.3 contains mktemp and mkstemp, both of which accept a
+# directory argument. Earlier versions of Python only contained
+# mktemp which didn't accept a directory argument. So we have to
+# implement our own versions here.
+if sys.version_info >= (2, 3, None, None):
+ # Just use the Python 2.3 built-in versions.
+ from tempfile import mktemp, mkstemp
+else:
+ def mktemp(suffix="", dir=None):
+ """
+ User-callable function to return a unique temporary file name.
+
+ Duplicated from Python's own tempfile with the optional "dir"
+ argument added. This allows customization of the directory, without
+ having to take over the module level variable, tempdir.
+ """
+ if not dir: dir = tempfile.gettempdir()
+ pre = tempfile.gettempprefix()
+ while 1:
+ i = tempfile._counter.get_next()
+ file = os.path.join(dir, pre + str(i) + suffix)
+ if not os.path.exists(file):
+ return file
+
+ def mkstemp(suffix="", dir=None):
+ """
+ User-callable function to return a tuple containing:
+ - a os-level file handle for the temp file, open for read/write
+ - the absolute path of that file
+
+ Note that this version of the function is not as secure as the
+ version included in Python 2.3.
+ """
+ path = mktemp(suffix, dir)
+ return os.open(path, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0600), path
+
+def wordWrap(s, width=78):
+ """
+ Returns a version of the string word wrapped to the given width.
+ Respects existing newlines in the string.
+
+ Taken from:
+ http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/148061
+ """
+ return reduce(
+ lambda line, word, width=width: "%s%s%s" % (
+ line,
+ ' \n'[(len(line[line.rfind('\n')+1:]) + len(word) >= width)],
+ word
+ ),
+ s.split(' ')
+ )
+
+
+def dateForEmail(now=None):
+ """ Returns a properly formatted date/time string for email messages """
+ if now is None:
+ now = time.localtime(time.time())
+ if now[8]==1:
+ offset = -time.altzone / 60
+ else:
+ offset = -time.timezone / 60
+ if offset<0:
+ plusminus = '-'
+ else:
+ plusminus = '+'
+ return time.strftime('%a, %d %b %Y %H:%M:%S ', now) + plusminus + '%02d%02d' % (abs(offset/60), abs(offset%60))
+
+
+def hostName():
+ """
+ Returns the host name which is taken first from the os environment and failing that, from the 'hostname' executable. May return None if neither attempt succeeded.
+ The environment keys checked are HOST and HOSTNAME both upper and lower case.
+ """
+ for name in ['HOST', 'HOSTNAME', 'host', 'hostname']:
+ hostName = os.environ.get(name, None)
+ if hostName:
+ break
+ if not hostName:
+ hostName = string.strip(os.popen('hostname').read())
+ if not hostName:
+ hostName = None
+ else:
+ hostName = string.lower(hostName)
+ return hostName
+
+
+_localIP = None
+
+def localIP(remote=('www.yahoo.com', 80), useCache=1):
+ """
+ Gets the "public" address of the local machine, i.e. that address
+ which is connected to the general Internet.
+
+ This function connects to a remote HTTP server the first time it is
+ invoked (or every time it is invoked with useCache=0). If that is
+ not acceptable, pass remote=None, but be warned that the result is
+ less likely to be externally visible.
+
+ Getting your local ip is actually quite complex. If this function
+ is not serving your needs then you probably need to think deeply
+ about what you really want and how your network is really set up.
+ Search comp.lang.python for "local ip" for more information.
+ http://groups.google.com/groups?q=%22local+ip%22+group:comp.lang.python.*
+ """
+ global _localIP
+ if useCache and _localIP:
+ return _localIP
+ import socket
+ if remote:
+ # code from Donn Cave on comp.lang.python
+
+ # My notes:
+ # Q: Why not use this? socket.gethostbyname(socket.gethostname())
+ # A: On some machines, it returns '127.0.0.1' - not what we had in mind.
+ #
+ # Q: Why not use this? socket.gethostbyname_ex(socket.gethostname())[2]
+ # A: Because some machines have more than one IP (think "VPN", etc.) and
+ # there is no easy way to tell which one is the externally visible IP.
+
+ try:
+ s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ s.connect(remote)
+ ip, port = s.getsockname()
+ s.close()
+ _localIP = ip
+ return _localIP
+ except socket.error:
+ # oh, well. we'll use the local method
+ pass
+
+ addresses = socket.gethostbyname_ex(socket.gethostname())[2]
+ for address in addresses:
+ if address!='127.0.0.1':
+ if useCache:
+ _localIP = address
+ return address
+ if useCache:
+ _localIP = addresses[0]
+ return _localIP
+
+
+def safeDescription(x, what='what'):
+ """
+ Returns the repr() of x and its class (or type) for help in
+ debugging. A major benefit here is that exceptions from
+ repr() are consumed. This is important in places like
+ "assert" where you don't want to lose the assertion
+ exception in your attempt to get more information.
+
+ Example use:
+ assert isinstance(foo, Foo), safeDescription(foo)
+ print "foo:", safeDescription(foo) # won't raise exceptions
+
+ # better output format:
+ assert isinstance(foo, Foo), safeDescription(foo, 'foo')
+ print safeDescription(foo, 'foo')
+ """
+ try:
+ xRepr = repr(x)
+ except Exception, e:
+ xRepr = _descExc('x', e)
+ if hasattr(x, '__class__'):
+ try:
+ cRepr = repr(x.__class__)
+ except Exception, e:
+ cRepr = _descExc('x.__class__', e)
+ return '%s=%s class=%s' % (what, xRepr, cRepr)
+ else:
+ try:
+ cRepr = repr(type(x))
+ except Exception, e:
+ cRepr = _descExc('type(x)', e)
+ return '%s=%s type=%s' % (what, xRepr, cRepr)
+
+def _descExc(reprOfWhat, e):
+ """
+ Returns a description of an exception. This is a private function
+ for use by safeDescription().
+ """
+ try:
+ return '(exception from repr(%s): %s: %s)' % (reprOfWhat, e.__class__, e)
+ except:
+ return '(exception from repr(%s))' % reprOfWhat
+
+def timestamp(numSecs=None):
+ """
+ Returns a dictionary whose keys give different versions of the timestamp:
+ 'numSecs': the number of seconds
+ 'tuple': (year, month, day, hour, min, sec)
+ 'pretty': 'YYYY-MM-DD HH:MM:SS'
+ 'condensed': 'YYYYMMDDHHMMSS'
+ 'dashed': 'YYYY-MM-DD-HH-MM-SS'
+ The focus is on the year, month, day, hour and second, with no additional information such as timezone or day of year. This form of timestamp is often ideal for print statements, logs and filenames.
+ If the current number of seconds is not passed, then the current time is taken.
+ The 'pretty' format is ideal for print statements, while the 'condensed' and 'dashed' formats are generally more appropriate for filenames.
+ """
+ if numSecs is None:
+ numSecs = time.time()
+ tuple = time.localtime(numSecs)[:6]
+ pretty = '%4i-%02i-%02i %02i:%02i:%02i' % tuple
+ condensed = '%4i%02i%02i%02i%02i%02i' % tuple
+ dashed = '%4i-%02i-%02i-%02i-%02i-%02i' % tuple
+ return locals()
+
+
+def uniqueId(forObject=None):
+ """
+ Generates an opaque, identifier string that is practically guaranteed to be unique.
+ If an object is passed, then its id() is incorporated into the generation.
+ Relies on md5 and returns a 32 character long string.
+ """
+ r = [time.time(), random.random(), os.times()]
+ if forObject is not None:
+ r.append(id(forObject))
+ md5object = md5.new(str(r))
+ try:
+ return md5object.hexdigest()
+ except AttributeError:
+ # Older versions of Python didn't have hexdigest, so we'll do it manually
+ hexdigest = []
+ for char in md5object.digest():
+ hexdigest.append('%02x' % ord(char))
+ return string.join(hexdigest, '')
+
+
+def valueForString(s):
+ """
+ For a given string, returns the most appropriate Pythonic value
+ such as None, a long, an int, a list, etc. If none of those
+ make sense, then returns the string as-is.
+
+ "None", "True" and "False" are case-insensitive because there is
+ already too much case sensitivity in computing, damn it!
+ """
+ if not s:
+ return s
+ try:
+ return int(s)
+ except ValueError:
+ pass
+ try:
+ return long(s)
+ except ValueError:
+ pass
+ try:
+ return float(s)
+ except ValueError:
+ pass
+ t = s.lower()
+ if t=='none':
+ return None
+ if t=='true':
+ return True
+ if t=='false':
+ return False
+ if s[0] in '[({"\'':
+ return eval(s)
+ return s
+
+
+### Deprecated
+
+def Commas(number):
+ print 'DEPRECATED: MiscUtils.Funcs.Commas() on 02/23/01 in ver 0.5. Use commas() instead.'
+ return commas(number)
+
+def CharWrap(s, width, hanging=0):
+ print 'DEPRECATED: MiscUtils.Funcs.CharWrap() on 02/23/01 in ver 0.5. Use charWrap() instead.'
+ return charWrap(s, width, hanging)
diff --git a/paste/webkit/FakeWebware/MiscUtils/M2PickleRPC.py b/paste/webkit/FakeWebware/MiscUtils/M2PickleRPC.py
new file mode 100644
index 0000000..2976f7f
--- /dev/null
+++ b/paste/webkit/FakeWebware/MiscUtils/M2PickleRPC.py
@@ -0,0 +1,67 @@
+"""
+M2Crypto-enhanced transport for PickleRPC
+
+This lets you use M2Crypto for SSL encryption.
+
+Based on m2xmlrpclib.py which is
+ Copyright (c) 1999-2002 Ng Pheng Siong. All rights reserved
+"""
+
+from PickleRPC import Transport
+import base64, string, sys
+from M2Crypto import SSL, httpslib, m2urllib
+try:
+ from cStringIO import StringIO
+except ImportError:
+ from StringIO import StringIO
+
+__version__ = 1 # version of M2PickleRPC
+
+class M2Transport(Transport):
+ user_agent = "M2PickleRPC.py/%s - %s" % (__version__, Transport.user_agent)
+
+ def __init__(self, ssl_context=None):
+ if ssl_context is None:
+ self.ssl_ctx=SSL.Context('sslv23')
+ else:
+ self.ssl_ctx=ssl_context
+
+ def make_connection(self, host):
+ _host, _port = m2urllib.splitport(host)
+ if sys.version[0] == '2':
+ return httpslib.HTTPS(_host, int(_port), ssl_context=self.ssl_ctx)
+ elif sys.version[:3] == '1.5':
+ return httpslib.HTTPS(self.ssl_ctx, _host, int(_port))
+ else:
+ raise RuntimeError, 'unsupported Python version'
+
+ # @@ workarounds below are necessary because M2Crypto seems to
+ # return from fileobject.read() early! So we have to call it
+ # over and over to get the full data.
+
+ def parse_response(self, f):
+ """
+ Workaround M2Crypto issue mentioned above
+ """
+ sio = StringIO()
+ while 1:
+ chunk = f.read()
+ if not chunk:
+ break
+ sio.write(chunk)
+ sio.seek(0)
+ return Transport.parse_response(self, sio)
+
+ def parse_response_gzip(self, f):
+ """
+ Workaround M2Crypto issue mentioned above
+ """
+ sio = StringIO()
+ while 1:
+ chunk = f.read()
+ if not chunk:
+ break
+ sio.write(chunk)
+ sio.seek(0)
+ return Transport.parse_response_gzip(self, sio)
+
diff --git a/paste/webkit/FakeWebware/MiscUtils/MixIn.py b/paste/webkit/FakeWebware/MiscUtils/MixIn.py
new file mode 100644
index 0000000..26dde1d
--- /dev/null
+++ b/paste/webkit/FakeWebware/MiscUtils/MixIn.py
@@ -0,0 +1,70 @@
+from types import MethodType
+import sys
+
+if hasattr(sys, 'version_info') and sys.version_info[0]>=2:
+ def MixIn(pyClass, mixInClass, makeAncestor=0, mixInSuperMethods=0):
+ """
+ Mixes in the attributes of the mixInClass into the pyClass. These attributes are typically methods (but don't have to be). Note that private attributes, denoted by a double underscore, are not mixed in. Collisions are resolved by the mixInClass' attribute overwriting the pyClass'. This gives mix-ins the power to override the behavior of the pyClass.
+
+ After using MixIn(), instances of the pyClass will respond to the messages of the mixInClass.
+
+ An assertion fails if you try to mix in a class with itself.
+
+ The pyClass will be given a new attribute mixInsForCLASSNAME which is a list of all mixInClass' that have ever been installed, in the order they were installed. You may find this useful for inspection and debugging.
+
+ You are advised to install your mix-ins at the start up of your program, prior to the creation of any objects. This approach will result in less headaches. But like most things in Python, you're free to do whatever you're willing to live with. :-)
+
+ There is a bitchin' article in the Linux Journal, April 2001, "Using Mix-ins with Python" by Chuck Esterbrook which gives a thorough treatment of this topic.
+
+ An example, that resides in Webware, is MiddleKit.Core.ModelUser.py, which install mix-ins for SQL adapters. Search for "MixIn(".
+
+ If makeAncestor is 1, then a different technique is employed: the mixInClass is made the first base class of the pyClass. You probably don't need to use this and if you do, be aware that your mix-in can no longer override attributes/methods in pyClass.
+
+ If mixInSuperMethods is 1, then support will be enabled for you to be able to call the original or
+ "parent" method from the mixed-in method. This is done like so:
+
+ class MyMixInClass:
+ def foo(self):
+ MyMixInClass.mixInSuperFoo(self) # call the original method
+ # now do whatever you want
+
+ This function only exists if you are using Python 2.0 or later. Python 1.5.2 has a problem where functions (as in aMethod.im_func) are tied to their class, when in fact, they should be totally generic with only the methods being tied to their class. Apparently this was fixed in Py 2.0.
+ """
+ assert mixInClass is not pyClass, 'mixInClass = %r, pyClass = %r' % (mixInClass, pyClass)
+ if makeAncestor:
+ if mixInClass not in pyClass.__bases__:
+ pyClass.__bases__ = (mixInClass,) + pyClass.__bases__
+ else:
+ # Recursively traverse the mix-in ancestor classes in order
+ # to support inheritance
+ baseClasses = list(mixInClass.__bases__)
+ baseClasses.reverse()
+ for baseClass in baseClasses:
+ MixIn(pyClass, baseClass)
+
+ # Track the mix-ins made for a particular class
+ attrName = 'mixInsFor'+pyClass.__name__
+ mixIns = getattr(pyClass, attrName, None)
+ if mixIns is None:
+ mixIns = []
+ setattr(pyClass, attrName, mixIns)
+
+ # Make sure we haven't done this before
+ # Er, woops. Turns out we like to mix-in more than once sometimes.
+ #assert not mixInClass in mixIns, 'pyClass = %r, mixInClass = %r, mixIns = %r' % (pyClass, mixInClass, mixIns)
+
+ # Record our deed for future inspection
+ mixIns.append(mixInClass)
+
+ # Install the mix-in methods into the class
+ for name in dir(mixInClass):
+ if not name.startswith('__'): # skip private members
+ member = getattr(mixInClass, name)
+
+ if type(member) is MethodType and mixInSuperMethods:
+ if hasattr(pyClass, name):
+ origmember = getattr(pyClass, name)
+ setattr(mixInClass, 'mixInSuper' + name[0].upper() + name[1:], origmember)
+ if type(member) is MethodType:
+ member = member.im_func
+ setattr(pyClass, name, member)
diff --git a/paste/webkit/FakeWebware/MiscUtils/NamedValueAccess.py b/paste/webkit/FakeWebware/MiscUtils/NamedValueAccess.py
new file mode 100644
index 0000000..62df004
--- /dev/null
+++ b/paste/webkit/FakeWebware/MiscUtils/NamedValueAccess.py
@@ -0,0 +1,570 @@
+"""
+NamedValueAccess provides functions, a mix-in class and a wrapper class
+all for accessing Python objects by named attributes. You can use which
+ever of the three approaches best suites your needs and style.
+
+
+NOTES
+
+If Python provided a root class 'Object' in the same tradition as other
+OOP languages such as Smalltalk, Objective-C and Java, then we could
+dispense with the global functions and simply stick with the mix-in.
+
+
+TO DO
+
+* The mix-in's valueForKey() could be out of slight alignment with the
+ function, since they have different implementations. However, the test
+ cases pass for both right now.
+
+* Should the valueForKey() function provide for caching of bindings in
+ the same manner than the mix-in does?
+
+ If not, should the mix-in allow an option to *not* cache bindings?
+
+* hasValueForKey() function? (We already have a method in the mix-in)
+
+* valuesForNames() in the mix-in:
+ * Change parameter 'keys' to 'names'
+ * Use NoDefault instead of None in the parameters
+ * Revisit doc string and test cases
+
+* Docs: More improvs to doc strings.
+
+* Testing: increase coverage
+
+* Rename? class NamedValueAccess+ible:
+
+* Benchmarking: Set this up in a new file:
+ Testing/BenchNamedValueAccess.py
+ so we can experment with caching vs. not and other techniques.
+
+
+PAST DESIGN DECISIONS
+
+* Only if a name binds to a method is it invoked. Another approach is
+ to invoke any value that is __call__able, but that is unPythonic: If
+ obj.foo is a class or a function then obj.foo gives that class or
+ function, not the result of invoking it. Method is the only
+ convenience we provide, because that's one of the major points of
+ providing this.
+
+
+CREDIT
+
+Chuck Esterbrook <echuck@mindspring.com>
+Tavis Rudd <tavis@calrudd.com>
+"""
+
+
+import types
+import string, sys
+from time import time
+from MiscUtils import NoDefault
+
+
+# if technique is zero, use bound methods in the _kvGetBindings cache, otherwise use unbound
+# @@ 2000-05-31 ce: after additional testing we can probably scorge the technique=0 allowance
+technique = 1
+
+
+## Exceptions ##
+
+class NamedValueAccessError(LookupError): pass
+class ValueForKeyError(NamedValueAccessError): pass
+
+
+class NamedValueAccess:
+ """
+ This class is intended to be ancestor class such that you can say:
+ from NamedValueAccess import *
+ age = someObj.valueForName("age")
+ name = someObj.valueForName("info.fields.name")
+
+ This can be useful in setups where you wish to textually refer to the objects
+ in a program, such as an HTML template processed in the context of an
+ object-oriented framework.
+
+ Keys can be matched to either methods or ivars and with or without underscores.
+
+ valueForName() can also traverse bona fide dictionaries (DictType).
+
+ You can safely import * from this module. Only the NamedValueAccess class is exported
+ (other than typical things like string and sys).
+
+ There is no __init__() method and never will be.
+
+ You can run the test suite by running this module as a program.
+
+ You'll see the terms 'key' and 'name' in the class and its documentation. A 'key'
+ is a single identifier such as 'foo'. A name could be key, or a qualified key,
+ such as 'foo.bar.boo'. Names are generally more convenient and powerful, while
+ key-oriented methods are more efficient and provide the atomic functionality that
+ name-oriented methods are built upon. From a usage point of view, you normally
+ just use the 'name' methods and forget about the 'key'.
+
+ @@ 2000-05-21 ce: This class causes problems when used in WebKit for logging.
+ Perhaps circular references?
+ Involving self?
+ Having to do with methods bound to their objects?
+
+ @@ 2000-03-03 ce: document ivars
+
+ @@ 2000-04-24 ce: Some classes like UserDict need to use getitem()
+ instead of getattr() and don't need to deal with _bindingForGetKey().
+
+ @@ 2000-05-31 ce: Rename this class to NamedValues, NamedValueAccess, ValuesByName
+
+ @@ This class probably needs to be in MiscUtils, as it's being used in that way
+ while MiddleKit was intended for "enterprise/business objects".
+ """
+
+ #
+ # Accessing values by key
+ #
+ def hasValueForKey(self, key):
+ """ Returns true if the key is available, although that does not
+ guarantee that there will not be errors caused by retrieving the key. """
+
+ return self._bindingForGetKey(key)!=None
+
+
+ def valueForKey(self, key, default=NoDefault):
+ """ Suppose key is 'foo'. This method returns the value with the following precedence:
+ 1. Methods before non-methods
+ 2. Public attributes before private attributes
+
+ More specifically, this method then returns one of the following:
+ * self.foo()
+ * self._foo()
+ * self.foo
+ * self._foo
+
+ ...or default, if it was specified,
+ otherwise invokes and returns result of valueForUnknownKey().
+ Note that valueForUnknownKey(), normally returns an exception.
+
+ See valueForName() which is a more advanced version of this method that allows
+ multiple, qualified keys.
+ """
+
+ binding = self._bindingForGetKey(key)
+
+ if not binding:
+ if default is NoDefault:
+ return self.valueForUnknownKey(key, default)
+ else:
+ return default
+
+ if type(binding) is types.MethodType:
+# @@ 2000-05-07 ce: come to a decision on exception handling for key errors
+# try:
+ if technique:
+ result = binding(self)
+ else:
+ result = binding()
+# except:
+ # @@ 2000-02-18: Improve next line with exception info
+# raise NamedValueAccessError, 'Caught exception while accessing key (%s). Exception is %s' % (key, sys.exc_info())
+ return result
+ else:
+ return getattr(self, binding)
+
+ def hasValueForName(self, keysString):
+ try:
+ value = self.valueForName(keysString)
+ except NamedValueAccessError:
+ return 0
+ return 1
+
+ def valueForName(self, keysString, default=None):
+ """ Returns the value for the given keysString. This is the more advanced version of
+ valueForKey(), which can only handle single names. This method can handle
+ 'foo', 'foo1.foo2', 'a.b.c.d', etc. It will traverse dictionaries if needed. """
+ keys = string.split(keysString, '.')
+ return self.valueForKeySequence(keys, default)
+
+ def valueForKeySequence(self, listOfKeys, default=None):
+ # @@ 2000-02-18: document
+ return _valueForKeySequence(self, listOfKeys, default)
+
+ def valuesForNames(self, keys, default=None, defaults=None, forgive=0, includeNames=0):
+ """ Returns a list of values that match the given keys, each of which is passed
+ through valueForName() and so could be of the form 'a.b.c'.
+ keys is a sequence. default is any kind of object. defaults is a sequence.
+ forgive and includeNames is a flag.
+ If default is not None, then it is substituted when a key is not found.
+ Otherwise, if defaults is not None, then it's corresponding/parallel value
+ for the current key is substituted when a key is not found.
+ Otherwise, if forgive=1, then unknown keys simply don't produce any values.
+ Otherwise, if default and defaults are None, and forgive=0, then the unknown
+ keys will probably raise an exception through self.valueForUnknownKey() although
+ that method can always return a final, default value.
+ if keys is None, then None is returned. If keys is an empty list, then None
+ is returned.
+ Often these last four arguments are specified by key.
+ Examples:
+ names = ['origin.x', 'origin.y', 'size.width', 'size.height']
+ obj.valuesForNames(names)
+ obj.valuesForNames(names, default=0.0)
+ obj.valuesForNames(names, defaults=[0.0, 0.0, 100.0, 100.0])
+ obj.valuesForNames(names, forgive=0)
+ @@ 2000-03-04 ce: includeNames is only supported when forgive=1.
+ It should be supported for the other cases.
+ It should be documented.
+ It should be included in the test cases.
+ """
+
+ if keys is None:
+ return None
+ if len(keys) is 0:
+ return []
+ results = []
+
+ if default is not None:
+ results = map(lambda key, myself=self, mydefault=default: myself.valueForName(key, mydefault), keys)
+ elif defaults is not None:
+ if len(keys) is not len(defaults):
+ raise NamedValueAccessError, 'Keys and defaults have mismatching lengths (%d and %d).' % (len(keys), len(defaults))
+ results = map(lambda key, default, myself=self: myself.valueForName(key, default), keys, defaults)
+ elif forgive:
+ results = []
+ uniqueObject = 'uni' + 'que'
+ for key in keys:
+ value = self.valueForName(key, uniqueObject)
+ if value is not uniqueObject:
+ if includeNames:
+ results.append((key, value))
+ else:
+ results.append(value)
+ else:
+ # no defaults, no forgiveness
+ results = map(lambda key, myself=self: myself.valueForName(key), keys)
+ return results
+
+ def setValueForKey(self, key, value):
+ # @@ 2000-02-18: naming might be weired here with args reversed
+ """ Suppose key is 'foo'. This method sets the value with the following precedence:
+ 1. Public attributes before private attributes
+ 2. Methods before non-methods
+
+ More specifically, this method then uses one of the following:
+ @@ 2000-03-04 ce: fill in
+
+ ...or invokes handleUnknownSetKey().
+ """
+ raise NotImplementedError # @@ 2000-03-04 ce
+
+ def resetKeyBindings(self):
+ # @@ 2000-02-18 document this method
+ if hasattr(self, '_kvGetBindings'):
+ self._kvGetBindings = {}
+
+
+ #
+ # Errors
+ #
+ def valueForUnknownKey(self, key, default):
+ raise NamedValueAccessError, key
+
+ #def handleUnknownSetKey(self, key):
+ # raise NamedValueAccessError, key
+
+
+ #
+ # Private
+ #
+ def _bindingForGetKey(self, key):
+ """ Bindings are cached.
+ Bindings are methods or strings.
+ """
+
+ # Make _kvGetBindings dictionary if we don't have one
+ if not hasattr(self, '_kvGetBindings'):
+ self._kvGetBindings = {}
+
+ # Return the binding if we already have one
+ if self._kvGetBindings.has_key(key):
+ return self._kvGetBindings[key]
+
+ # No binding, so we have to look for the key
+
+ found = None # set to what we find
+
+ # Try plain old key
+ if hasattr(self, key):
+ found = getattr(self, key)
+ #print '0: found = ', found, type(found)
+ if type(found) is not types.MethodType:
+ found = key
+ elif technique:
+ found = getattr(self.__class__, key)
+ self._kvGetBindings[key] = found
+ #print '1: found = ', found, type(found)
+
+ # Try _key only if we didn't find a method called key
+ if type(found) is not types.MethodType:
+ underKey = '_' + key
+ if hasattr(self, underKey):
+ underAttr = getattr(self, underKey)
+ if found==None:
+ if type(underAttr) is types.MethodType:
+ if technique:
+ value = getattr(self.__class__, underKey)
+ else:
+ value = underAttr
+ else:
+ value = underKey
+ found = self._kvGetBindings[key] = value
+ else:
+ if type(underAttr) is types.MethodType:
+ if technique:
+ underAttr = getattr(self.__class__, underKey)
+ found = self._kvGetBindings[key] = underAttr
+
+ #print '2: found = ', found, type(found)
+
+ return found
+
+
+class NamedValueAccessWrapper(NamedValueAccess):
+ """
+ This provides a wrapper around an existing object which will respond
+ to the methods of NamedValueAccess. By using the wrapper, you can
+ stick with objects and methods such as obj.valueForName('x.y') (as
+ opposed to functions like valueForName()) and refrain from modifying
+ the existing class hierarchy with NamedValueAccess.
+
+ Example:
+ wrapper = NamedValueAccessWrapper(obj)
+ print wrapper.valueForName('manager.name')
+ """
+
+ def __init__(self, object):
+ self._object = object
+
+ def hasValueForKey(self, key):
+ try:
+ value = self.valueForKey(ley)
+ except NamedValueAccessError:
+ return 0
+ else:
+ return 1
+
+ def valueForKey(self, key, default=NoDefault):
+ return valueForKey(self._object)
+
+ def valueForName(self, key, default=NoDefault):
+ return valueForName(self._object)
+
+
+
+#
+# Private
+#
+
+def _valueForKeySequence(obj, listOfKeys, default=None):
+ """ This is a recursive function used to implement NamedValueAccess.valueForKeySequence.
+ Besides supporting inheritors of NamedValueAccess, this function also supports
+ dictionaries, which is why it's not found in the class.
+ """
+
+ # @@ 2000-02-18: Optimize by specifying index instead of making new list
+ if type(obj) is types.DictType:
+ try:
+ value = obj[listOfKeys[0]]
+ except: # @@ 2000-03-03 ce: this exception should be more specific. probably nameerror or indexerror
+ if default is None:
+ raise NamedValueAccessError, 'Unknown key (%s) in dictionary.' % listOfKeys[0]
+ else:
+ return default
+ else:
+ value = obj.valueForKey(listOfKeys[0], default)
+ if len(listOfKeys)>1:
+ return _valueForKeySequence(value, listOfKeys[1:], default)
+ else:
+ return value
+
+
+def _dict_valueForKey(obj, key, default=NoDefault):
+ """
+ Returns the value for a given key of the dictionary-like object.
+ This is a private, custom function built in support of valueForKey().
+ """
+ try:
+ value = obj[key]
+ except AttributeError, e:
+ # We attempt to pass only on exceptions caused
+ # by obj not responding to __getitem__. Any
+ # other exceptions generated get raised up.
+ substring = "instance has no attribute '__getitem__'"
+ if e.args[0][-len(substring):]==substring:
+ if default is NoDefault:
+ return None
+ else:
+ return
+ else:
+ raise
+ except KeyError, e:
+ if e.args[0]==key:
+ if default is NoDefault:
+ raise ValueForKeyError, key
+ else:
+ return default
+ else:
+ # If we get here, then the KeyError is deeper in the
+ # implementation of obj[key]
+ raise
+ else:
+ return value
+
+
+def valueForKey(obj, key, default=NoDefault):
+ """
+ Returns the value of the object named by the given key.
+
+ Suppose key is 'foo'. This method returns the value with the
+ following precedence:
+ 1. Methods before non-methods
+ 2. Attributes before keys (__getitem__)
+ 3. Public things before private things
+ (private being denoted by a preceding underscore)
+
+ More specifically, this method returns one of the following:
+ * obj.valueForKey(key) # only if the method exists
+ * obj.foo()
+ * obj._foo()
+ * obj.foo
+ * obj._foo
+ * obj['foo']
+ * obj.valueForUnknownKey(key)
+ * default # only if specified
+
+ If all of these fail, a ValueForKeyError is raised.
+
+
+ NOTES
+
+ * If the object provides a valueForKey() method, that method will be
+ invoked to do the work.
+
+ * valueForKey() works on dictionaries and dictionary-like objects.
+
+ * valueForUnknownKey() provides a hook by which objects can
+ delegate or chain their keyed value access to other objects.
+ The key and default arguments are passed to it and it should
+ generally respect the typical treatment of the the default
+ argument as found throughout Webware and described in the Style
+ Guidelines.
+
+ * See valueForName() which is a more advanced version of this
+ function that allows multiple, qualified keys.
+ """
+
+ # We only accept strings for keys
+ assert type(key) is types.StringType
+
+ # Use obj.valueForKey() if it is available
+ valueForKeyMeth = getattr(obj, 'valueForKey', None)
+ if valueForKeyMeth:
+ return valueForKeyMeth(key, default)
+
+ attr = None
+ method = None
+ value = None
+ unknown = 0
+ if type(obj) is types.DictType:
+ if default is NoDefault:
+ try:
+ return obj[key]
+ except KeyError:
+ raise ValueForKeyError, key
+ else:
+ return obj.get(key, default)
+ else:
+ try:
+ klass = obj.__class__
+ except AttributeError:
+ raise AttributeError, '__class__ obj type=%r, obj=%r' % (type(obj), obj)
+ method = getattr(klass, key, None)
+ if not method:
+ underKey = '_' + key
+ method = getattr(klass, underKey, None)
+ if not method:
+ attr = getattr(obj, key, NoDefault)
+ if attr is NoDefault:
+ attr = getattr(obj, underKey, NoDefault)
+ if attr is NoDefault:
+ getitem = getattr(obj.__class__, '__getitem__', None)
+ if getitem:
+ try:
+ value = getitem(obj, key)
+ except KeyError:
+ unknown = 1
+
+# if value is not NoDefault:
+# return value
+ if not unknown:
+ if method:
+ return method(obj)
+ if attr is not NoDefault:
+ return attr
+
+ # Use obj.valueForUnknownKey() if it is available
+ valueForUnknownKey = getattr(obj, 'valueForUnknownKey', None)
+ if valueForUnknownKey:
+ return valueForUnknownKey(key, default)
+
+ if default!=NoDefault:
+ return default
+ else:
+ raise ValueForKeyError, key
+
+
+def valueForName(obj, name, default=NoDefault):
+ """
+ Returns the value of the object that is named. The name can use
+ dotted notation to traverse through a network/graph of objects.
+ Since this function relies on valueForKey() for each individual
+ component of the name, you should be familiar with the semantics
+ of that notation.
+
+ Example: valueForName(obj, 'department.manager.salary')
+ """
+
+ names = string.split(name, '.')
+ for name in names:
+ obj = valueForKey(obj, name, default)
+ if obj is default:
+ return obj
+ # 2001-04-19 ce: I suppose the above technique could result in
+ # the default being returned prematurely if it was part of the
+ # chain of names. Well, that's just the way it goes for now.
+ return obj
+
+
+# Beef up UserDict with the NamedValueAccess base class and custom versions of
+# hasValueForKey() and valueForKey(). This all means that UserDict's (such as
+# os.environ) are key/value accessible.
+# @@ 2000-05-07 ce: CGIWrapper.py duplicates this.
+def _enhanceUserDict():
+ from UserDict import UserDict
+ if not NamedValueAccess in UserDict.__bases__:
+ UserDict.__bases__ = UserDict.__bases__ + (NamedValueAccess,)
+
+ def _UserDict_hasValueForKey(self, key):
+ return self.has_key(key)
+
+ def _UserDict_valueForKey(self, key, default=NoDefault):
+ if default is NoDefault:
+ if self.has_key(key):
+ return self[key]
+ else:
+ raise ValueForKeyError, key
+ else:
+ return self.get(key, default)
+
+ setattr(UserDict, 'hasValueForKey', _UserDict_hasValueForKey)
+ setattr(UserDict, 'valueForKey', _UserDict_valueForKey)
+
+_enhanceUserDict()
diff --git a/paste/webkit/FakeWebware/MiscUtils/ParamFactory.py b/paste/webkit/FakeWebware/MiscUtils/ParamFactory.py
new file mode 100644
index 0000000..4b3f2f3
--- /dev/null
+++ b/paste/webkit/FakeWebware/MiscUtils/ParamFactory.py
@@ -0,0 +1,22 @@
+from threading import Lock
+
+class ParamFactory:
+ def __init__(self, klass, **extraMethods):
+ self.lock = Lock()
+ self.cache = {}
+ self.klass = klass
+ for name, func in extraMethods.items():
+ setattr(self, name, func)
+ def __call__(self, *args):
+ self.lock.acquire()
+ if not self.cache.has_key(args):
+ value = self.klass(*args)
+ self.cache[args] = value
+ self.lock.release()
+ return value
+ else:
+ self.lock.release()
+ return self.cache[args]
+ def allInstances(self):
+ return self.cache.values()
+
diff --git a/paste/webkit/FakeWebware/MiscUtils/PickleCache.py b/paste/webkit/FakeWebware/MiscUtils/PickleCache.py
new file mode 100644
index 0000000..e826b89
--- /dev/null
+++ b/paste/webkit/FakeWebware/MiscUtils/PickleCache.py
@@ -0,0 +1,220 @@
+"""
+PickleCache provides tools for keeping fast-loading cached versions of
+files so that subsequent loads are faster. This is similar to how Python
+silently caches .pyc files next to .py files.
+
+The typical scenario is that you have a type of text file that gets
+"translated" to Pythonic data (dictionaries, tuples, instances, ints,
+etc.). By caching the Python data on disk in pickle format, you can
+avoid the expensive translation on subsequent reads of the file.
+
+Two real life cases are MiscUtils.DataTable, which loads and represents
+comma-separated files, and MiddleKit which has an object model file.
+So for examples on using this module, load up the following files and
+search for "Pickle":
+ Webware/MiscUtils/DataTable.py
+ MiddleKit/Core/Model.py
+
+The cached file is named the same as the original file with
+'.pickle.cache' suffixed. The utility of '.pickle' is to denote the file
+format and the utilty of '.cache' is to provide '*.cache' as a simple
+pattern that can be removed, ignored by backup scripts, etc.
+
+The treatment of the cached file is silent and friendly just like
+Python's approach to .pyc files. If it cannot be read or written for
+various reasons (cache is out of date, permissions are bad, wrong python
+version, etc.), then it will be silently ignored.
+
+
+GRANULARITY
+
+In constructing the test suite, I discovered that if the source file is
+newly written less than 1 second after the cached file, then the fact
+that the source file is newer will not be detected and the cache will
+still be used. I believe this is a limitation of the granularity of
+os.path.getmtime(). If anyone knows of a more granular solution, please
+let me know.
+
+This would only be a problem in programmatic situations where the source
+file was rapidly being written and read. I think that's fairly rare.
+
+
+PYTHON VERSION
+
+These operations do nothing if you don't have Python 2.2 or greater.
+
+
+SEE ALSO
+ http://www.python.org/doc/current/lib/module-pickle.html
+
+- wordwrap bar --------------------------------------------------------
+"""
+
+verbose = 0
+
+
+import os, sys, time
+from types import DictType
+from pprint import pprint
+try:
+ from cPickle import load, dump
+except ImportError:
+ from pickle import load, dump
+
+havePython22OrGreater = sys.version_info[0]>2 or (sys.version_info[0]==2 and sys.version_info[1]>=2)
+
+
+s ="""
+def readPickleCache(filename, pickleVersion=1, source=None, verbose=None):
+ return _reader.read(filename, pickleVersion, source, verbose)
+
+def writePickleCache(data, filename, pickleVersion=1, source=None, verbose=None):
+ return _writer.write(data, filename, pickleVersion, source, verbose)
+"""
+
+
+class PickleCache:
+ """
+ Just a simple abstract base class for PickleCacheReader and
+ PickleCacheWriter.
+ """
+ verbose = verbose
+
+ def picklePath(self, filename):
+ return filename + '.pickle.cache'
+
+
+class PickleCacheReader(PickleCache):
+
+ def read(self, filename, pickleVersion=1, source=None, verbose=None):
+ """
+ Returns the data from the pickle cache version of the filename, if it can read. Otherwise returns None which also indicates that writePickleCache() should be subsequently called after the original file is read.
+ """
+ if verbose is None:
+ v = self.verbose
+ else:
+ v = verbose
+ if v: print '>> PickleCacheReader.read() - verbose is on'
+ assert filename
+
+ if not os.path.exists(filename):
+ #if v: print 'cannot find %r' % filename
+ open(filename) # to get a properly constructed IOError
+
+ if not havePython22OrGreater:
+ #if v: print 'Python version is too old for this. Returning None.'
+ return None
+
+ didReadPickle = 0
+ shouldDeletePickle = 0
+
+ data = None
+
+ picklePath = self.picklePath(filename)
+ if os.path.exists(picklePath):
+ if os.path.getmtime(picklePath)<os.path.getmtime(filename):
+ #if v: print 'cache is out of date'
+ shouldDeletePickle = 1
+ else:
+ try:
+ #if v: print 'about to open for read %r' % picklePath
+ file = open(picklePath)
+ except IOError, e:
+ #if v: print 'cannot open cache file: %s: %s' % (e.__class__.__name__, e)
+ pass
+ else:
+ try:
+ #if v: print 'about to load'
+ dict = load(file)
+ except EOFError:
+ #if v: print 'EOFError - not loading'
+ shouldDeletePickle = 1
+ else:
+ file.close()
+ #if v: print 'finished reading'
+ assert isinstance(dict, DictType), 'type=%r dict=%r' % (type(dict), dict)
+ for key in ('source', 'data', 'pickle version', 'python version'):
+ assert dict.has_key(key), key
+ if source and dict['source']!=source:
+ #if v: print 'not from required source (%s): %s' % (source, dict['source'])
+ shouldDeletePickle = 1
+ elif dict['pickle version']!=pickleVersion:
+ #if v: print 'pickle version (%i) does not match expected (%i)' % (dict['pickle version'], pickleVersion)
+ shouldDeletePickle = 1
+ elif dict['python version']!=sys.version_info:
+ #if v: print 'python version %s does not match current %s' % (dict['python version'], sys.version_info)
+ shouldDeletePickle = 1
+ else:
+ #if v: print 'all tests pass. accepting data'
+ if v>1:
+ print 'display full dict:'
+ pprint(dict)
+ data = dict['data']
+ didReadPickle = 1
+
+ # delete the pickle file if suggested by previous conditions
+ if shouldDeletePickle:
+ try:
+ #if v: print 'attempting to remove pickle cache file'
+ os.remove(picklePath)
+ except OSError, e:
+ if v: print 'failed to remove: %s: %s' % (e.__class__.__name__, e)
+ pass
+
+ if v: print 'done reading data'; print
+
+ return data
+
+
+class PickleCacheWriter(PickleCache):
+
+ writeSleepInterval = 0.1
+
+ def write(self, data, filename, pickleVersion=1, source=None, verbose=None):
+ if verbose is None:
+ v = self.verbose
+ else:
+ v = verbose
+ if v: print '>> PickleCacheWriter.write() - verbose is on'
+ assert filename
+ sourceTimestamp = os.path.getmtime(filename)
+
+ if not havePython22OrGreater:
+ #if v: print 'Python version is too old for this. Returning None.'
+ return None
+
+ picklePath = self.picklePath(filename)
+ dict = {
+ 'source': source,
+ 'python version': sys.version_info,
+ 'pickle version': pickleVersion,
+ 'data': data,
+ }
+ if v>1:
+ print 'display full dict:'
+ pprint(dict)
+ try:
+ if v: print 'about to open for write %r' % picklePath
+ file = open(picklePath, 'w')
+ except IOError, e:
+ if v: print 'error. not writing. %s: %s' % (e.__class__.__name__, e)
+ else:
+ while 1:
+ dump(dict, file, 1) # 1 = binary format
+ file.close()
+ # make sure the cache has a newer timestamp, otherwise the cache will
+ # just get ignored and rewritten next time.
+ if os.path.getmtime(picklePath)==sourceTimestamp:
+ if v: print 'timestamps are identical. sleeping %0.2f seconds' % self.writeSleepInterval
+ time.sleep(self.writeSleepInterval)
+ file = open(picklePath, 'w')
+ else:
+ break
+
+ if v: print 'done writing data'; print
+
+
+# define module level convenience functions, readPickleCache and writePickleCache:
+
+_reader = PickleCacheReader(); readPickleCache = _reader.read
+_writer = PickleCacheWriter(); writePickleCache = _writer.write
diff --git a/paste/webkit/FakeWebware/MiscUtils/PickleRPC.py b/paste/webkit/FakeWebware/MiscUtils/PickleRPC.py
new file mode 100644
index 0000000..c74f651
--- /dev/null
+++ b/paste/webkit/FakeWebware/MiscUtils/PickleRPC.py
@@ -0,0 +1,428 @@
+"""
+PickleRPC provides a Server object for connection to Pickle-RPC servers
+for the purpose of making requests and receiving the responses.
+
+ >>> from MiscUtils.PickleRPC import Server
+ >>> server = Server('http://localhost/cgi-bin/WebKit.cgi/Examples/PickleRPCExample')
+ >>> server.multiply(10,20)
+ 200
+ >>> server.add(10,20)
+ 30
+
+
+See also: Server, Webkit.PickleRPCServlet, WebKit.Examples.PickleRPCExample
+
+
+UNDER THE HOOD
+
+Requests look like this:
+ {
+ 'version': 1, # default
+ 'action': 'call', # default
+ 'methodName': 'NAME',
+ 'args': (A, B, ...), # default = (,)
+ 'keywords': {'A': A, 'B': B, ...} # default = {}
+ }
+
+Only 'methodName' is required since that is the only key without a
+default value.
+
+Responses look like this:
+ {
+ 'timeReceived': N,
+ 'timeReponded': M,
+ 'value': V,
+ 'exception': E,
+ 'requestError': E,
+ }
+
+TimeReceived is the time the initial request was received.
+TimeResponded is the time at which the response was finished, as
+close to transmission as possible. The times are expressed as
+number of seconds since the Epoch, e.g., time.time().
+
+Value is whatever the method happened to return.
+
+Exception may be 'occurred' to indicate that an exception
+occurred, the specific exception, such as "KeyError: foo" or the
+entire traceback (as a string), at the discretion of the server.
+It will always be a non-empty string if it is present.
+
+RequestError is an exception such as "Missing method
+in request." (with no traceback) that indicates a problem with the
+actual request received by the Pickle-RPC server.
+
+Value, exception and requestError are all exclusive to each other.
+
+
+SECURITY
+
+Pickle RPC uses the SafeUnpickler class (in this module) to
+prevent unpickling of unauthorized classes. By default, it
+doesn't allow _any_ classes to be unpickled. You can override
+allowedGlobals() or findGlobal() in a subclass as needed to
+allow specific class instances to be unpickled.
+
+Note that both Transport in this module and PickleRPCServlet in
+WebKit are derived from SafeUnpickler.
+
+
+CREDIT
+
+The implementation of this module was taken directly from Python 2.2's
+xmlrpclib and then transformed from XML-orientation to Pickle-orientation.
+
+The zlib compression was adapted from code by Skip Montanaro that I found
+here: http://manatee.mojam.com/~skip/python/
+"""
+
+
+__version__ = 1 # version of PickleRPC protocol
+
+import types
+
+try:
+ from cPickle import dumps, Unpickler, UnpicklingError
+except ImportError:
+ from pickle import dumps, Unpickler, UnpicklingError
+
+try:
+ import zlib
+except ImportError:
+ zlib = None
+
+try:
+ from cStringIO import StringIO
+except ImportError:
+ from StringIO import StringIO
+
+class Error(Exception):
+ """
+ The abstract exception/error class for all PickleRPC errors.
+ """
+ pass
+
+
+class ResponseError(Error):
+ """
+ These are unhandled exceptions raised when the server was computing
+ a response. These will indicate errors such as:
+ * exception in the actual target method on the server
+ * malformed responses
+ * non "200 OK" status code responses
+ """
+ pass
+
+
+# Sometimes xmlrpclib is installed as a package, sometimes not. So we'll
+# make sure it works either way.
+try:
+ from xmlrpclib.xmlrpclib import ProtocolError as _PE
+except ImportError:
+ from xmlrpclib import ProtocolError as _PE
+# @@ 2002-01-31 ce: should this be caught somewhere for special handling? Perhaps in XMLRPCServlet?
+
+class ProtocolError(ResponseError, _PE):
+ pass
+
+
+class RequestError(Error):
+ """
+ These are errors originally raised by the server complaining about
+ malformed requests.
+ """
+ pass
+
+
+class InvalidContentTypeError(ResponseError):
+
+ def __init__(self, headers, content):
+ Exception.__init__(self) #, headers, content)
+ self.headers = headers
+ self.content = content
+
+ def __repr__(self):
+ content = self.content
+ return '%s: Content type is not text/x-python-pickled-dict\nheaders = %s\ncontent =\n%s' % (
+ self.__class__.__name__, self.headers, content)
+
+ __str__ = __repr__
+
+
+class SafeUnpickler:
+ """
+ For security reasons, we don't want to allow just anyone to unpickle
+ anything. That can cause arbitrary code to be executed.
+ So this SafeUnpickler base class is used to control
+ what can be unpickled. By default it doesn't let you unpickle
+ any class instances at all, but you can create subclass that
+ overrides allowedGlobals().
+
+ Note that the PickleRPCServlet class in WebKit is derived from this class
+ and uses its load() and loads() methods to do all unpickling.
+ """
+ def allowedGlobals(self):
+ """
+ Must return a list of (moduleName, klassName) tuples for all
+ classes that you want to allow to be unpickled.
+
+ Example:
+ return [('mx.DateTime', '_DT')]
+ allows mx.DateTime instances to be unpickled.
+ """
+ return []
+
+ def findGlobal(self, module, klass):
+ if (module, klass) not in self.allowedGlobals():
+ raise UnpicklingError, 'For security reasons, you can\'t unpickle objects from module %s with type %s' % (module, klass)
+ globals = {}
+ exec 'from %s import %s as theClass' % (module, klass) in globals
+ return globals['theClass']
+
+ def load(self, file):
+ safeUnpickler = Unpickler(file)
+ safeUnpickler.find_global = self.findGlobal
+ return safeUnpickler.load()
+
+ def loads(self, str):
+ return self.load(StringIO(str))
+
+
+# @@ 2002-01-31 ce: Could we reduce code duplication and automatically
+# inherit future improvements by actually importing and using the
+# xmlrpclib classes below either as base classes or mix-ins?
+
+
+class Server:
+ """uri [,options] -> a logical connection to an XML-RPC server
+
+ uri is the connection point on the server, given as
+ scheme://host/target.
+
+ The standard implementation always supports the "http" scheme. If
+ SSL socket support is available (Python 2.0), it also supports
+ "https".
+
+ If the target part and the slash preceding it are both omitted,
+ "/PickleRPC" is assumed.
+
+ See the module doc string for more information.
+ """
+
+ def __init__(self, uri, transport=None, verbose=0, binary=1, compressRequest=1, acceptCompressedResponse=1):
+ # establish a "logical" server connection
+
+ # get the url
+ import urllib
+ type, uri = urllib.splittype(uri)
+ if type not in ("http", "https"):
+ raise IOError, "unsupported Pickle-RPC protocol"
+ self.__host, self.__handler = urllib.splithost(uri)
+ if not self.__handler:
+ self.__handler = "/PickleRPC"
+
+ if transport is None:
+ if type == "https":
+ transport = SafeTransport()
+ else:
+ transport = Transport()
+ self.__transport = transport
+
+ self.__verbose = verbose
+ self.__binary = binary
+ self.__compressRequest = compressRequest
+ self.__acceptCompressedResponse = acceptCompressedResponse
+
+ def _request(self, methodName, args, keywords):
+ """
+ Call a method on the remote server.
+ """
+ request = {
+ 'version': 1,
+ 'action': 'call',
+ 'methodName': methodName,
+ 'args': args,
+ 'keywords': keywords,
+ }
+ if self.__binary:
+ request = dumps(request, 1)
+ else:
+ request = dumps(request)
+ if zlib is not None and self.__compressRequest and len(request) > 1000:
+ request = zlib.compress(request, 1)
+ compressed = 1
+ else:
+ compressed = 0
+
+ response = self.__transport.request(
+ self.__host,
+ self.__handler,
+ request,
+ verbose=self.__verbose,
+ binary=self.__binary,
+ compressed=compressed,
+ acceptCompressedResponse=self.__acceptCompressedResponse
+ )
+
+ return response
+
+ def __requestValue(self, methodName, args, keywords):
+ dict = self._request(methodName, args, keywords)
+ if dict.has_key('value'):
+ return dict['value']
+ elif dict.has_key('exception'):
+ raise ResponseError, dict['exception']
+ elif dict.has_key('requestError'):
+ raise RequestError, dict['requestError']
+ else:
+ raise RequestError, 'Response does not have a value, expection or requestError.'
+
+ def __repr__(self):
+ return "<%s for %s%s>" % (self.__class__.__name__, self.__host, self.__handler)
+
+ __str__ = __repr__
+
+ def __getattr__(self, name):
+ # magic method dispatcher
+ return _Method(self.__requestValue, name)
+
+ ## note: to call a remote object with an non-standard name, use
+ ## result getattr(server, "strange-python-name")(args)
+
+
+ServerProxy = Server # be like xmlrpclib for those who might guess or expect it
+
+
+
+class _Method:
+ """
+ Some magic to bind a Pickle-RPC method to an RPC server.
+ Supports "nested" methods (e.g. examples.getStateName).
+ """
+
+ def __init__(self, send, name):
+ self.__send = send
+ self.__name = name
+
+ def __getattr__(self, name):
+ return _Method(self.__send, "%s.%s" % (self.__name, name))
+
+ def __call__(self, *args, **keywords): # note that keywords are supported
+ return self.__send(self.__name, args, keywords)
+
+
+class Transport(SafeUnpickler):
+ """
+ Handles an HTTP transaction to a Pickle-RPC server.
+ """
+
+ # client identifier (may be overridden)
+ user_agent = "PickleRPC/%s (by http://webware.sf.net/)" % __version__
+
+ def request(self, host, handler, request_body, verbose=0, binary=0, compressed=0,
+ acceptCompressedResponse=0):
+ # issue Pickle-RPC request
+
+ h = self.make_connection(host)
+ if verbose:
+ h.set_debuglevel(1)
+
+ self.send_request(h, handler, request_body)
+ self.send_host(h, host)
+ self.send_user_agent(h)
+ self.send_content(h, request_body, binary, compressed, acceptCompressedResponse)
+
+ errcode, errmsg, headers = h.getreply()
+
+ if errcode != 200:
+ raise ProtocolError(
+ host + handler,
+ errcode, errmsg,
+ headers
+ )
+
+ self.verbose = verbose
+
+ if h.headers['content-type'] not in ['text/x-python-pickled-dict', 'application/x-python-binary-pickled-dict']:
+ headers = h.headers.headers
+ content = h.getfile().read()
+ raise InvalidContentTypeError(headers, content)
+
+ try:
+ content_encoding = headers["content-encoding"]
+ if content_encoding and content_encoding == "x-gzip":
+ return self.parse_response_gzip(h.getfile())
+ elif content_encoding:
+ raise ProtocolError(host + handler,
+ 500,
+ "Unknown encoding type: %s" %
+ content_encoding,
+ headers)
+ else:
+ return self.parse_response(h.getfile())
+ except KeyError:
+ return self.parse_response(h.getfile())
+
+ def make_connection(self, host):
+ # create a HTTP connection object from a host descriptor
+ import httplib
+ return httplib.HTTP(host)
+
+ def send_request(self, connection, handler, request_body):
+ connection.putrequest("POST", handler)
+
+ def send_host(self, connection, host):
+ connection.putheader("Host", host)
+
+ def send_user_agent(self, connection):
+ connection.putheader("User-Agent", self.user_agent)
+
+ def send_content(self, connection, request_body, binary=0, compressed=0,
+ acceptCompressedResponse=0):
+ if binary:
+ connection.putheader("Content-Type", "application/x-python-binary-pickled-dict")
+ else:
+ connection.putheader("Content-Type", "text/x-python-pickled-dict")
+ connection.putheader("Content-Length", str(len(request_body)))
+ if compressed:
+ connection.putheader("Content-Encoding", "x-gzip")
+ if zlib is not None and acceptCompressedResponse:
+ connection.putheader("Accept-Encoding", "gzip")
+ connection.endheaders()
+ if request_body:
+ connection.send(request_body)
+
+ def parse_response(self, f):
+ return self.load(f)
+
+ def parse_response_gzip(self, f):
+ # read response from input file, decompress it, and parse it
+ # @@ gat: could this be made more memory-efficient?
+ return self.loads(zlib.decompress(f.read()))
+
+class SafeTransport(Transport):
+ """
+ Handles an HTTPS transaction to a Pickle-RPC server.
+ """
+
+ def make_connection(self, host):
+ # create a HTTPS connection object from a host descriptor
+ # host may be a string, or a (host, x509-dict) tuple
+ import httplib
+ if isinstance(host, types.TupleType):
+ host, x509 = host
+ else:
+ x509 = {}
+ try:
+ HTTPS = httplib.HTTPS
+ except AttributeError:
+ raise NotImplementedError,\
+ "your version of httplib doesn't support HTTPS"
+ else:
+ return apply(HTTPS, (host, None), x509)
+
+ def send_host(self, connection, host):
+ if isinstance(host, types.TupleType):
+ host, x509 = host
+ connection.putheader("Host", host)
+
diff --git a/paste/webkit/FakeWebware/MiscUtils/PropertiesObject.py b/paste/webkit/FakeWebware/MiscUtils/PropertiesObject.py
new file mode 100644
index 0000000..d32f1a3
--- /dev/null
+++ b/paste/webkit/FakeWebware/MiscUtils/PropertiesObject.py
@@ -0,0 +1,155 @@
+from UserDict import UserDict
+import os, string, sys, types
+
+class WillNotRunError(Exception): pass
+
+
+class PropertiesObject(UserDict):
+ """
+ A PropertiesObject represents, in a dictionary-like fashion, the values found in a Properties.py file. That file is always included with a Webware component to advertise its name, version, status, etc. Note that a Webware component is a Python package that follows additional conventions. Also, the top level Webware directory contains a Properties.py.
+
+ Component properties are often used for:
+ * generation of documentation
+ * runtime examination of components, especially prior to loading
+
+ PropertiesObject provides additional keys:
+ * filename - the filename from which the properties were read
+ * versionString - a nicely printable string of the version
+ * requiredPyVersionString - like versionString but for requiredPyVersion instead
+ * willRun - 1 if the component will run. So far that means having the right Python version.
+ * willNotRunReason - defined only if willRun is 0. contains a readable error message
+
+ Using a PropertiesObject is better than investigating the Properties.py file directly, because the rules for determining derived keys and any future convenience methods will all be provided here.
+
+ Usage example:
+ from MiscUtils.PropertiesObject import PropertiesObject
+ props = PropertiesObject(filename)
+ for item in props.items():
+ print '%s: %s' % item
+
+ Note: We don't normally suffix a class name with "Object" as we have with this class, however, the name Properties.py is already used in our containing package and all other packages.
+ """
+
+
+ ## Init and reading ##
+
+ def __init__(self, filename=None):
+ UserDict.__init__(self)
+ if filename:
+ self.readFileNamed(filename)
+
+ def loadValues(self, dict):
+ self.update(dict)
+ self.cleanPrivateItems()
+
+
+ def readFileNamed(self, filename):
+ self['filename'] = filename
+ results = {}
+ exec open(filename) in results
+ # @@ 2001-01-20 ce: try "...in self"
+ self.update(results)
+ self.cleanPrivateItems()
+ self.createDerivedItems()
+
+
+ ## Self utility ##
+
+ def cleanPrivateItems(self):
+ """ Removes items whose keys start with a double underscore, such as __builtins__. """
+ for key in self.keys():
+ if key[:2]=='__':
+ del self[key]
+
+ def createDerivedItems(self):
+ self.createVersionString()
+ self.createRequiredPyVersionString()
+ self.createWillRun()
+
+ def _versionString(self, version):
+ """ For a sequence containing version information such as (2, 0, 0, 'pre'), this returns a printable string such as '2.0-pre'. The micro version number is only excluded from the string if it is zero. """
+ ver = map(lambda x: str(x), version)
+ if ver[2]=='0': # e.g., if minor version is 0
+ numbers = ver[:2]
+ else:
+ numbers = ver[:3]
+ rest = ver[3:]
+ numbers = string.join(numbers, '.')
+ rest = string.join(rest, '-')
+ if rest:
+ return numbers + rest
+ else:
+ return numbers
+
+ def createVersionString(self):
+ self['versionString'] = self._versionString(self['version'])
+
+ def createRequiredPyVersionString(self):
+ self['requiredPyVersionString'] = self._versionString(self['requiredPyVersion'])
+
+ def createWillRun(self):
+ self['willRun'] = 0
+ try:
+ # Invoke each of the checkFoo() methods
+ for key in self.willRunKeys():
+ methodName = 'check' + string.upper(key[0]) + key[1:]
+ method = getattr(self, methodName)
+ method()
+ except WillNotRunError, msg:
+ self['willNotRunReason'] = msg
+ return
+ self['willRun'] = 1 # we passed all the tests
+
+ def willRunKeys(self):
+ """ Returns a list of keys whose values should be examined in order to determine if the component will run. Used by createWillRun(). """
+ return ['requiredPyVersion', 'requiredOpSys', 'deniedOpSys', 'willRunFunc']
+
+ def checkRequiredPyVersion(self):
+ pyVer = getattr(sys, 'version_info', None)
+ if not pyVer:
+ # Prior 2.0 there was no version_info
+ # So we parse it out of .version which is a string
+ pyVer = string.split(sys.version)[0]
+ pyVer = string.split(pyVer, '.')
+ pyVer = map(lambda x: int(x), pyVer)
+ if tuple(pyVer)<tuple(self['requiredPyVersion']):
+ raise WillNotRunError, 'Required python ver is %s, but actual ver is %s.' % (self['requiredPyVersion'], pyVer)
+
+ def checkRequiredOpSys(self):
+ requiredOpSys = self.get('requiredOpSys', None)
+ if requiredOpSys:
+ # We accept a string or list of strings
+ if type(requiredOpSys)==types.StringType:
+ requiredOpSys = [requiredOpSys]
+ if not os.name in requiredOpSys:
+ raise WillNotRunError, 'Required op sys is %s, but actual op sys is %s.' % (requiredOpSys, os.name)
+
+ def checkDeniedOpSys(self):
+ deniedOpSys = self.get('deniedOpSys', None)
+ if deniedOpSys:
+ # We accept a string or list of strings
+ if type(deniedOpSys)==types.StringType:
+ deniedOpSys = [deniedOpSys]
+ if os.name in deniedOpSys:
+ raise WillNotRunError, 'Will not run on op sys %s and actual op sys is %s.' % (deniedOpSys, os.name)
+
+ def checkRequiredSoftware(self):
+ """ Not implemented. No op right now. """
+ # Check required software
+ # @@ 2001-01-24 ce: TBD
+ # Issues include:
+ # - order of dependencies
+ # - circular dependencies
+ # - examining Properties and willRun of dependencies
+ reqSoft = self.get('requiredSoftware', None)
+ if reqSoft:
+ for soft in reqSoft:
+ # type, name, version
+ pass
+
+ def checkWillRunFunc(self):
+ willRunFunc = self.get('willRunFunc', None)
+ if willRunFunc:
+ whyNotMsg = willRunFunc()
+ if whyNotMsg:
+ raise WillNotRunError, whyNotMsg
diff --git a/paste/webkit/FakeWebware/MiscUtils/__init__.py b/paste/webkit/FakeWebware/MiscUtils/__init__.py
new file mode 100644
index 0000000..0f39efb
--- /dev/null
+++ b/paste/webkit/FakeWebware/MiscUtils/__init__.py
@@ -0,0 +1,68 @@
+# MiscUtils component
+# Webware for Python
+# See Docs/index.html
+
+__all__ = ['Configurable', 'DBPool', 'DataTable', 'DictForArgs', 'Error', 'Funcs', 'MixIn', 'NamedValueAccess', 'PropertiesObject', 'unittest']
+
+
+try:
+ AbstractError # Python might build this in some day.
+except NameError:
+ class AbstractError(NotImplementedError):
+ """
+ This exception is raised by abstract methods in abstract classes. It
+ is a special case of NotImplementedError, that indicates that the
+ implementation won't ever be provided at that location in the future
+ --instead the subclass should provide it.
+
+ Typical usage:
+
+ from MiscUtils import AbstractError
+
+ class Foo:
+ def bar(self):
+ raise AbstractError, self.__class__
+
+ Note that added the self.__class__ makes the resulting exception
+ *much* more useful.
+ """
+ pass
+
+# @@ 2002-11-10 ce: SubclassResponsibilityError is deprecated in favor of AbstractError, post 0.7
+SubclassResponsibilityError = AbstractError
+
+
+class NoDefault:
+ """
+ This provides a singleton "thing" which can be used to initialize
+ the "default=" arguments for different retrieval methods. For
+ example:
+
+ from MiscUtils import NoDefault
+ def bar(self, name, default=NoDefault):
+ if default is NoDefault:
+ return self._bars[name] # will raise exception for invalid key
+ else:
+ return self._bars.get(name, default)
+
+ The value None does not suffice for "default=" because it does not
+ indicate whether or not a value was passed.
+
+ Consistently using this singleton is valuable due to subclassing
+ situations:
+
+ def bar(self, name, default=NoDefault):
+ if someCondition:
+ return self.specialBar(name)
+ else:
+ return SuperClass.bar(name, default)
+
+ It's also useful if one method that uses "default=NoDefault" relies
+ on another object and method to which it must pass the default.
+ (This is similar to the subclassing situation.)
+ """
+ pass
+
+
+def InstallInWebKit(appServer):
+ pass
diff --git a/paste/webkit/FakeWebware/README.txt b/paste/webkit/FakeWebware/README.txt
new file mode 100644
index 0000000..d6a7943
--- /dev/null
+++ b/paste/webkit/FakeWebware/README.txt
@@ -0,0 +1,7 @@
+This directory can be added to sys.path so that all your Webware
+imports will still work, but will import WSGIWebKit versions of the
+objects.
+
+Items will be added here on an as-needed basis. I don't want to bring
+every public object from Webware into this setup; in part because many
+of them are not currently implemented.
diff --git a/paste/webkit/FakeWebware/WebKit/HTTPServlet.py b/paste/webkit/FakeWebware/WebKit/HTTPServlet.py
new file mode 100644
index 0000000..104d2ec
--- /dev/null
+++ b/paste/webkit/FakeWebware/WebKit/HTTPServlet.py
@@ -0,0 +1 @@
+from paste.webkit.wkservlet import HTTPServlet
diff --git a/paste/webkit/FakeWebware/WebKit/Page.py b/paste/webkit/FakeWebware/WebKit/Page.py
new file mode 100644
index 0000000..2bb01ed
--- /dev/null
+++ b/paste/webkit/FakeWebware/WebKit/Page.py
@@ -0,0 +1 @@
+from paste.webkit.wkservlet import Page
diff --git a/paste/webkit/FakeWebware/WebKit/__init__.py b/paste/webkit/FakeWebware/WebKit/__init__.py
new file mode 100644
index 0000000..792d600
--- /dev/null
+++ b/paste/webkit/FakeWebware/WebKit/__init__.py
@@ -0,0 +1 @@
+#
diff --git a/paste/webkit/FakeWebware/WebUtils/Funcs.py b/paste/webkit/FakeWebware/WebUtils/Funcs.py
new file mode 100644
index 0000000..d467257
--- /dev/null
+++ b/paste/webkit/FakeWebware/WebUtils/Funcs.py
@@ -0,0 +1,148 @@
+"""
+WebUtils.Funcs provides some basic functions that are useful in HTML and web development.
+
+You can safely import * from WebUtils.Funcs if you like.
+
+
+TO DO
+
+* Document the 'codes' arg of htmlEncode/Decode.
+
+"""
+
+import string
+
+
+htmlCodes = [
+ ['&', '&amp;'],
+ ['<', '&lt;'],
+ ['>', '&gt;'],
+ ['"', '&quot;'],
+# ['\n', '<br>']
+]
+
+htmlCodesReversed = htmlCodes[:]
+htmlCodesReversed.reverse()
+
+
+def htmlEncode(s, codes=htmlCodes):
+ """ Returns the HTML encoded version of the given string. This is useful to display a plain ASCII text string on a web page."""
+ for code in codes:
+ s = string.replace(s, code[0], code[1])
+ return s
+
+def htmlDecode(s, codes=htmlCodesReversed):
+ """ Returns the ASCII decoded version of the given HTML string. This does NOT remove normal HTML tags like <p>. It is the inverse of htmlEncode(). """
+ for code in codes:
+ s = string.replace(s, code[1], code[0])
+ return s
+
+
+
+_urlEncode = {}
+for i in range(256):
+ _urlEncode[chr(i)] = '%%%02x' % i
+for c in string.letters + string.digits + '_,.-/':
+ _urlEncode[c] = c
+_urlEncode[' '] = '+'
+
+def urlEncode(s):
+ """ Returns the encoded version of the given string, safe for using as a URL. """
+ return string.join(map(lambda c: _urlEncode[c], list(s)), '')
+
+def urlDecode(s):
+ """ Returns the decoded version of the given string. Note that invalid URLs will throw exceptons. For example, a URL whose % coding is incorrect. """
+ mychr = chr
+ atoi = string.atoi
+ parts = string.split(string.replace(s, '+', ' '), '%')
+ for i in range(1, len(parts)):
+ part = parts[i]
+ parts[i] = mychr(atoi(part[:2], 16)) + part[2:]
+ return string.join(parts, '')
+
+
+def htmlForDict(dict, addSpace=None, filterValueCallBack=None, maxValueLength=None):
+ """ Returns an HTML string with a <table> where each row is a key-value pair. """
+ keys = dict.keys()
+ keys.sort()
+ # A really great (er, bad) example of hardcoding. :-)
+ html = ['<table width=100% border=0 cellpadding=2 cellspacing=2>']
+ for key in keys:
+ value = dict[key]
+ if addSpace!=None and addSpace.has_key(key):
+ target = addSpace[key]
+ value = string.join(string.split(value, target), '%s '%target)
+ if filterValueCallBack:
+ value = filterValueCallBack(value, key, dict)
+ value = str(value)
+ if maxValueLength and len(value) > maxValueLength:
+ value = value[:maxValueLength] + '...'
+ html.append('<tr bgcolor=#F0F0F0> <td> %s </td> <td> %s &nbsp;</td> </tr>\n' % (htmlEncode(str(key)), htmlEncode(value)))
+ html.append('</table>')
+ return string.join(html, '')
+
+
+def requestURI(dict):
+ """ Returns the request URI for a given CGI-style dictionary. Uses REQUEST_URI if available, otherwise constructs and returns it from SCRIPT_NAME, PATH_INFO and QUERY_STRING. """
+ uri = dict.get('REQUEST_URI', None)
+ if uri==None:
+ uri = dict.get('SCRIPT_NAME', '') + dict.get('PATH_INFO', '')
+ query = dict.get('QUERY_STRING', '')
+ if query!='':
+ uri = uri + '?' + query
+ return uri
+
+def normURL(path):
+ """Normalizes a URL path, like os.path.normpath, but acts on
+ a URL independant of operating system environmant.
+ """
+ if not path:
+ return
+
+ initialslash = path[0] == '/'
+ lastslash = path[-1] == '/'
+ comps = string.split(path, '/')
+
+ newcomps = []
+ for comp in comps:
+ if comp in ('','.'):
+ continue
+ if comp != '..':
+ newcomps.append(comp)
+ elif newcomps:
+ newcomps.pop()
+ path = string.join(newcomps, '/')
+ if path and lastslash:
+ path = path + '/'
+ if initialslash:
+ path = '/' + path
+ return path
+
+### Deprecated
+
+HTMLCodes = htmlCodes
+HTMLCodesReversed = htmlCodesReversed
+
+def HTMLEncode(s):
+ print 'DEPRECATED: WebUtils.Funcs.HTMLEncode() on 02/24/01 in ver 0.3. Use htmlEncode() instead.'
+ return htmlEncode(s)
+
+def HTMLDecode(s):
+ print 'DEPRECATED: WebUtils.Funcs.HTMLDecode() on 02/24/01 in ver 0.3. Use htmlDecode() instead.'
+ return htmlDecode(s)
+
+def URLEncode(s):
+ print 'DEPRECATED: WebUtils.Funcs.URLEncode() on 02/24/01 in ver 0.3. Use urlEncode() instead.'
+ return urlEncode(s)
+
+def URLDecode(s):
+ print 'DEPRECATED: WebUtils.Funcs.URLDecode() on 02/24/01 in ver 0.3. Use urlDecode() instead.'
+ return urlDecode(s)
+
+def HTMLForDictionary(dict, addSpace=None):
+ print 'DEPRECATED: WebUtils.Funcs.HTMLForDictionary() on 02/24/01 in ver 0.3. Use htmlForDict() instead.'
+ return htmlForDict(dict, addSpace)
+
+def RequestURI(dict):
+ print 'DEPRECATED: WebUtils.Funcs.RequestURI() on 02/24/01 in ver 0.3. Use requestURI() instead.'
+ return requestURI(dict)
diff --git a/paste/webkit/FakeWebware/WebUtils/__init__.py b/paste/webkit/FakeWebware/WebUtils/__init__.py
new file mode 100644
index 0000000..792d600
--- /dev/null
+++ b/paste/webkit/FakeWebware/WebUtils/__init__.py
@@ -0,0 +1 @@
+#
diff --git a/paste/webkit/FakeWebware/__init__.py b/paste/webkit/FakeWebware/__init__.py
new file mode 100644
index 0000000..792d600
--- /dev/null
+++ b/paste/webkit/FakeWebware/__init__.py
@@ -0,0 +1 @@
+#
diff --git a/paste/webkit/__init__.py b/paste/webkit/__init__.py
new file mode 100644
index 0000000..792d600
--- /dev/null
+++ b/paste/webkit/__init__.py
@@ -0,0 +1 @@
+#
diff --git a/paste/webkit/examples/EchoServlet.py b/paste/webkit/examples/EchoServlet.py
new file mode 100644
index 0000000..6dc30c6
--- /dev/null
+++ b/paste/webkit/examples/EchoServlet.py
@@ -0,0 +1,57 @@
+r"""\
+Paste/WebKit application
+
+Does things as requested. Takes variables:
+
+header.header-name=value, like
+ header.location=http://yahoo.com
+
+error=code, like
+ error=301 (temporary redirect)
+ error=assert (assertion error)
+
+environ=true,
+ display all the environmental variables, like
+ key=str(value)\n
+
+message=string
+ display string
+"""
+
+# Special WSGI version of WebKit:
+from paste.webkit.wkservlet import Page
+from paste import httpexceptions
+
+class EchoServlet(Page):
+
+ def writeHTML(self):
+ req = self.request()
+ headers = {}
+ for key, value in req.fields().items():
+ if key.startswith('header.'):
+ name = key[len('header.'):]
+ self.response().setHeader(name, value)
+ # @@: I shouldn't have to do this:
+ headers[name] = value
+
+ error = req.field('error', None)
+ if error and error != 'iter':
+ if error == 'assert':
+ assert 0, "I am asserting zero!"
+ raise httpexceptions.get_exception(
+ int(error), headers=headers)
+
+ if req.field('environ', None):
+ items = req.environ().items()
+ items.sort()
+ self.response().setHeader('content-type', 'text/plain')
+ for name, value in items:
+ self.write('%s=%s\n' % (name, value))
+ return
+
+ if req.hasField('message'):
+ self.response().setHeader('content-type', 'text/plain')
+ self.write(req.field('message'))
+ return
+
+ self.write('hello world!')
diff --git a/paste/webkit/examples/__init__.py b/paste/webkit/examples/__init__.py
new file mode 100644
index 0000000..792d600
--- /dev/null
+++ b/paste/webkit/examples/__init__.py
@@ -0,0 +1 @@
+#
diff --git a/paste/webkit/test_wkfixture.py b/paste/webkit/test_wkfixture.py
new file mode 100644
index 0000000..790a099
--- /dev/null
+++ b/paste/webkit/test_wkfixture.py
@@ -0,0 +1,66 @@
+"""
+Test fixture for Paste/WebKit testing.
+
+Maybe look at paste.tests.fixture as an alternative to this
+"""
+
+from cStringIO import StringIO
+from paste import recursive
+from paste import session
+from paste import httpexceptions
+from paste import lint
+from paste import wsgilib
+
+_default_environ = {
+ 'SCRIPT_NAME': '',
+ 'SERVER_NAME': 'localhost',
+ 'SERVER_PORT': '80',
+ 'REQUEST_METHOD': 'GET',
+ 'HTTP_HOST': 'localhost:80',
+ 'CONTENT_LENGTH': '0',
+ 'wsgi.input': StringIO(''),
+ 'wsgi.version': (1, 0),
+ 'wsgi.multithread': False,
+ 'wsgi.multiprocess': False,
+ 'wsgi.run_once': False,
+}
+
+def stack(application):
+ """
+ Creates a WebKit stack, except with a fixed application and
+ no URLParser.
+ """
+ return recursive.middleware(
+ lint.middleware(
+ httpexceptions.middleware(
+ lint.middleware(
+ session.middleware(
+ lint.middleware(application))))))
+
+def in_request(application, **kw):
+ """
+ Used to wrap a function in an application invokation, used like::
+
+ @in_request(my_app, path_info='/whatever',
+ wsgi__input='post-data',
+ REQUEST_METHOD='POST')
+ def test_this(output, **kw):
+ assert 'I got post-data' in output
+
+ The wrapped function received several keyword arguments, and in
+ the future there may be more, so a **kw is required so that
+ extra arguments may be ignored.
+ """
+ def decorator(func):
+ def replacement_func(*inner_args, **inner_kw):
+ # Note, *inner_args and **inner_kw are intended for use
+ # with @param or other py.test parameterizers
+ app = stack(application)
+ status, headers, output, errors = wsgilib.raw_interactive(
+ app, **kw)
+ return func(status=status, headers=headers, output=output,
+ errors=errors, application=application,
+ *inner_args,
+ **inner_kw)
+ return replacement_func
+ return decorator
diff --git a/paste/webkit/wkapplication.py b/paste/webkit/wkapplication.py
new file mode 100644
index 0000000..1c016a4
--- /dev/null
+++ b/paste/webkit/wkapplication.py
@@ -0,0 +1,18 @@
+"""
+A mostly dummy class to simulate the Webware Application object.
+"""
+
+from wkcommon import NoDefault
+
+class Application(object):
+
+ def __init__(self, transaction):
+ self._transaction = transaction
+
+ def forward(self, trans, url, context=None):
+ assert context is None, "Contexts are not supported"
+ trans.forward(url)
+
+ def setting(self, setting, default=NoDefault):
+ assert default is not NoDefault, "No settings are defined"
+ return default
diff --git a/paste/webkit/wkcommon.py b/paste/webkit/wkcommon.py
new file mode 100644
index 0000000..be25502
--- /dev/null
+++ b/paste/webkit/wkcommon.py
@@ -0,0 +1,187 @@
+import cgi
+import urllib
+import warnings
+import inspect
+import Cookie as CookieEngine
+
+__all__ = ['NoDefault', 'htmlEncode', 'htmlDecode',
+ 'urlEncode', 'urlDecode',
+ ]
+
+try:
+ from MiscUtils import NoDefault
+except ImportError:
+ class NoDefault:
+ pass
+
+def htmlEncode(s):
+ return cgi.escape(s, 1)
+
+def htmlDecode(s):
+ for char, code in [('&', '&amp;'),
+ ('<', '&lt;'),
+ ('>', '&gt;'),
+ ('"', '&quot;')]:
+ s = s.replace(code, char)
+ return s
+
+urlDecode = urllib.unquote
+urlEncode = urllib.quote
+
+def requestURI(dict):
+ """
+ Returns the request URI for a given CGI-style dictionary. Uses
+ REQUEST_URI if available, otherwise constructs and returns it from
+ SCRIPT_NAME, PATH_INFO and QUERY_STRING.
+ """
+ uri = dict.get('REQUEST_URI', None)
+ if uri is None:
+ uri = dict.get('SCRIPT_NAME', '') + dict.get('PATH_INFO', '')
+ query = dict.get('QUERY_STRING', '')
+ if query:
+ uri = uri + '?' + query
+ return uri
+
+def deprecated(msg=None):
+ if not msg:
+ frame = inspect.stack()[1]
+ methodName = frame[3]
+ msg = 'The use of %s is deprecated' % methodName
+ warnings.warn(msg, DeprecationWarning, stacklevel=3)
+
+
+
+class Cookie:
+ """
+ Cookie is used to create cookies that have additional
+ attributes beyond their value.
+
+ Note that web browsers don't typically send any information
+ with the cookie other than it's value. Therefore
+ `HTTPRequest.cookie` simply returns a value such as an
+ integer or a string.
+
+ When the server sends cookies back to the browser, it can send
+ a cookie that simply has a value, or the cookie can be
+ accompanied by various attributes (domain, path, max-age, ...)
+ as described in `RFC 2109`_. Therefore, in HTTPResponse,
+ `setCookie` can take either an instance of the Cookie class,
+ as defined in this module, or a value.
+
+ Note that Cookies values get pickled (see the `pickle` module),
+ so you can set and get cookies that are integers, lists,
+ dictionaries, etc.
+
+ .. _`RFC 2109`: ftp://ftp.isi.edu/in-notes/rfc2109.txt
+ """
+
+ ## Future
+ ##
+ ## * This class should provide error checking in the setFoo()
+ ## methods. Or maybe our internal Cookie implementation
+ ## already does that?
+ ## * This implementation is probably not as efficient as it
+ ## should be, [a] it works and [b] the interface is stable.
+ ## We can optimize later.
+
+ def __init__(self, name, value):
+ """
+ Create a cookie -- properties other than `name` and
+ `value` are set with methods.
+ """
+
+ self._cookies = CookieEngine.SimpleCookie()
+ self._name = name
+ self._value = value
+ self._cookies[name] = value
+ self._cookie = self._cookies[name]
+
+ """
+ **Accessors**
+ """
+
+ def comment(self):
+ return self._cookie['comment']
+
+ def domain(self):
+ return self._cookie['domain']
+
+ def maxAge(self):
+ return self._cookie['max-age']
+
+ def expires(self):
+ return self._cookie['expires']
+
+ def name(self):
+ return self._name
+
+ def path(self):
+ return self._cookie['path']
+
+ def isSecure(self):
+ return self._cookie['secure']
+
+ def value(self):
+ return self._value
+
+ def version(self):
+ return self._cookie['version']
+
+
+ """
+ **Setters**
+ """
+
+ def setComment(self, comment):
+ self._cookie['comment'] = comment
+
+ def setDomain(self, domain):
+ self._cookie['domain'] = domain
+
+ def setExpires(self, expires):
+ self._cookie['expires'] = expires
+
+ def setMaxAge(self, maxAge):
+ self._cookie['max-age'] = maxAge
+
+ def setPath(self, path):
+ self._cookie['path'] = path
+
+ def setSecure(self, bool):
+ self._cookie['secure'] = bool
+
+ def setValue(self, value):
+ self._value = value
+ self._cookies[self._name] = value
+
+ def setVersion(self, version):
+ self._cookie['version'] = version
+
+
+ """
+ **Misc**
+ """
+
+ def delete(self):
+ """
+ When sent, this should delete the cookie from the user's
+ browser, by making it empty, expiring it in the past,
+ and setting its max-age to 0. One of these will delete
+ the cookie for any browser (which one actually works
+ depends on the browser).
+ """
+
+ self._value = ''
+ self._cookie['expires'] = "Mon, 01-Jan-1900 00:00:00 GMT"
+ self._cookie['max-age'] = 0
+ self._cookie['path'] = '/'
+
+
+ def headerValue(self):
+ """
+ Returns a string with the value that should be
+ used in the HTTP headers. """
+
+ items = self._cookies.items()
+ assert(len(items)==1)
+ return items[0][1].OutputString()
diff --git a/paste/webkit/wkrequest.py b/paste/webkit/wkrequest.py
new file mode 100644
index 0000000..6b1785e
--- /dev/null
+++ b/paste/webkit/wkrequest.py
@@ -0,0 +1,344 @@
+"""
+A Webware HTTPRequest object, implemented based on the WSGI request
+environment dictionary.
+"""
+
+import time
+import traceback
+import cgi
+from wkcommon import NoDefault, requestURI, deprecated
+from Cookie import SimpleCookie as Cookie
+
+class HTTPRequest(object):
+
+ def __init__(self, transaction, environ):
+ self._environ = environ
+ self._transaction = transaction
+ if environ.has_key('webkit.time'):
+ self._time = environ['webkit.time']
+ else:
+ self._time = time.time()
+ self._input = environ['wsgi.input']
+ self._setupPath()
+ self._setupFields()
+ self._setupCookies()
+ self._pathInfo = None
+ self._serverRootPath = ""
+ # @@: I'm leaving out automatic path sessions
+ self._sessionExpired = False
+
+ def _setupPath(self):
+ self._environ['PATH_INFO'] = self._environ.get('PATH_INFO', '')
+ if not self._environ.has_key('REQUEST_URI'):
+ self._environ['REQUEST_URI'] = requestURI(self._environ)
+ # @@: Not necessarily true for WSGI:
+ self._adapterName = self._environ.get('SCRIPT_NAME')
+
+ def _setupFields(self):
+ self._fieldStorage = cgi.FieldStorage(
+ self._input,
+ environ=self._environ,
+ keep_blank_values=True,
+ strict_parsing=False)
+ try:
+ keys = self._fieldStorage.keys()
+ except TypeError:
+ # Maybe an XML-RPC request
+ keys = []
+ dict = {}
+ for key in keys:
+ value = self._fieldStorage[key]
+ if not isinstance(value, list):
+ if not value.filename:
+ # Turn the MiniFieldStorage into a string:
+ value = value.value
+ else:
+ value = [v.value for v in value]
+ dict[key] = value
+ if self._environ['REQUEST_METHOD'].upper() == 'POST':
+ # Then we must also parse GET variables
+ self._getFields = cgi.parse_qs(
+ self._environ.get('QUERY_STRING', ''),
+ keep_blank_values=True,
+ strict_parsing=False)
+ for name, value in self._getFields.items():
+ if not dict.has_key(name):
+ if isinstance(value, list) and len(value) == 1:
+ # parse_qs always returns a list of lists,
+ # while FieldStorage only uses lists for
+ # keys that actually repeat; this fixes that.
+ value = value[0]
+ dict[name] = value
+ self._fields = dict
+
+ def _setupCookies(self):
+ cookies = Cookie()
+ if self._environ.has_key('HTTP_COOKIE'):
+ try:
+ cookies.load(self._environ['HTTP_COOKIE'])
+ except:
+ traceback.print_exc(file=self._environ['wsgi.errors'])
+ dict = {}
+ for key in cookies.keys():
+ dict[key] = cookies[key].value
+ self._cookies = dict
+
+ def protocol(self):
+ return 'HTTP/1.0'
+
+ def time(self):
+ return self._time
+
+ def timeStamp(self):
+ return time.asctime(time.localtime(self.time()))
+
+ ## Transactions ##
+
+ def transaction(self):
+ return self._transaction
+
+ def setTransaction(self, trans):
+ self._transaction = trans
+
+ ## Values ##
+
+ def value(self, name, default=NoDefault):
+ if self._fields.has_key(name):
+ return self._fields[name]
+ else:
+ return self.cookie(name, default)
+
+ def hasValue(self, name):
+ return self._fields.has_key(name) or self._cookies.has_key(name)
+
+ def extraURLPath(self):
+ return self._environ.get('PATH_INFO', '')
+
+ ## Fields ##
+
+
+ def fieldStorage(self):
+ return self._fieldStorage
+
+ def field(self, name, default=NoDefault):
+ if default is NoDefault:
+ return self._fields[name]
+ else:
+ return self._fields.get(name, default)
+
+ def hasField(self, name):
+ return self._fields.has_key(name)
+
+ def fields(self):
+ return self._fields
+
+ def setField(self, name, value):
+ self._fields[name] = value
+
+ def delField(self, name):
+ del self._fields[name]
+
+ ## Cookies ##
+
+ def cookie(self, name, default=NoDefault):
+ """ Returns the value of the specified cookie. """
+ if default is NoDefault:
+ return self._cookies[name]
+ else:
+ return self._cookies.get(name, default)
+
+ def hasCookie(self, name):
+ return self._cookies.has_key(name)
+
+ def cookies(self):
+ """
+ Returns a dictionary-style object of all Cookie objects the
+ client sent with this request."""
+ return self._cookies
+
+ ## Variables passed by server ##
+ def serverDictionary(self):
+ """
+ Returns a dictionary with the data the web server gave us,
+ like HTTP_HOST or HTTP_USER_AGENT. """
+ return self._environ
+
+ ## Sessions ##
+
+ def session(self):
+ """ Returns the session associated with this request, either
+ as specified by sessionId() or newly created. This is a
+ convenience for transaction.session() """
+ return self._transaction.session()
+
+ def isSessionExpired(self):
+ """ Returns bool: whether or not this request originally
+ contained an expired session ID. Only works if the
+ Application.config setting "IgnoreInvalidSession" is set to 1;
+ otherwise you get a canned error page on an invalid session,
+ so your servlet never gets processed. """
+ return self._sessionExpired
+
+ def setSessionExpired(self, sessionExpired):
+ self._sessionExpired = sessionExpired
+
+ ## Authentication ##
+
+ def remoteUser(self):
+ """ Always returns None since authentication is not yet
+ supported. Take from CGI variable REMOTE_USER. """
+ # @@ 2000-03-26 ce: maybe belongs in section below. clean up docs
+ return self._environ['REMOTE_USER']
+
+ ## Remote info ##
+
+ def remoteAddress(self):
+ """ Returns a string containing the Internet Protocol (IP)
+ address of the client that sent the request. """
+ return self._environ['REMOTE_ADDR']
+
+ def remoteName(self):
+ """ Returns the fully qualified name of the client that sent
+ the request, or the IP address of the client if the name
+ cannot be determined. """
+ env = self._environ
+ return env.get('REMOTE_NAME', env['REMOTE_ADDR'])
+
+ ## Path ##
+
+ def urlPath(self):
+ raise NotImplementedError
+
+ def originalURLPath(self):
+ environ = self._environ.get('recursive.previous_environ', self._environ)
+ url = environ.get("SCRIPT_NAME", '') + environ.get('PATH_INFO', '')
+ #self._environ['wsgi.errors'].write('Original URL: %r (from %r)\n' % (url, environ))
+ return url
+
+ def urlPathDir(self):
+ raise NotImplementedError
+
+ def getstate(self):
+ raise NotImplementedError
+
+ def setURLPath(self, path):
+ raise NotImplementedError
+
+ def serverSidePath(self, path=None):
+ raise NotImplementedError
+
+ def serverSideContextPath(self, path=None):
+ base = self._environ['paste.config']['webkit_dir']
+ if path:
+ return os.path.join(base, path)
+ else:
+ return base
+
+ def contextName(self):
+ return ''
+
+ def servletURI(self):
+ """This is the URI of the servlet, without any query strings or extra path info"""
+ # @@: should be implemented
+ raise NotImplementedError
+
+ def uriWebKitRoot(self):
+ raise NotImplementedError
+
+ def fsPath(self):
+ raise NotImplementedError
+
+ def serverURL(self):
+ raise NotImplementedError
+
+ def serverURLDir(self):
+ raise NotImplementedError
+
+ def siteRoot(self):
+ raise NotImplementedError
+
+ def siteRootFromCurrentServlet(self):
+ raise NotImplementedError
+
+ def servletPathFromSiteRoot(self):
+ raise NotImplementedError
+
+ ## Special ##
+
+ def adapterName(self):
+ """
+ Returns the name of the adapter as it appears in the URL.
+ Example: '/WebKit.cgi'
+ This is useful in special cases when you are constructing URLs. See Testing/Main.py for an example use.
+ """
+ deprecated()
+ return '/'.join(self._environ['SCRIPT_NAME'].split('/')[:-1])
+
+ def rawRequest(self):
+ raise NotImplementedError
+
+ def environ(self):
+ return self._environ
+
+ def rawInput(self, rewind=0):
+ """
+ This gives you a file-like object for the data that was
+ sent with the request (e.g., the body of a POST request,
+ or the documented uploaded in a PUT request).
+
+ The file might not be rewound to the beginning if there
+ was valid, form-encoded POST data. Pass rewind=1 if
+ you want to be sure you get the entire body of the request.
+ """
+ fs = self.fieldStorage()
+ if rewind:
+ fs.file.seek(0)
+ return fs.file
+
+ ## Information ##
+
+ # @@ 2000-05-10: See FUTURE section of class doc string
+
+ def servletPath(self):
+ raise NotImplementedError
+
+ def contextPath(self):
+ raise NotImplementedError
+
+ def pathInfo(self):
+ raise NotImplementedError
+
+ def pathTranslated(self):
+ raise NotImplementedError
+
+ def queryString(self):
+ """
+ Returns the query string portion of the URL for this
+ request. Taken from the CGI variable QUERY_STRING. """
+ return self._environ.get('QUERY_STRING', '')
+
+ def uri(self):
+ """
+ Returns the request URI, which is the entire URL except
+ for the query string. """
+ return self._environ['REQUEST_URI']
+
+ def method(self):
+ """
+ Returns the HTTP request method (in all uppercase), typically
+ from the set GET, POST, PUT, DELETE, OPTIONS and TRACE."""
+ return self._environ['REQUEST_METHOD'].upper()
+
+ def sessionId(self):
+ """ Returns a string with the session id specified by the
+ client, or None if there isn't one. """
+ sid = self.value('_SID_', None)
+ return sid
+
+ ## Inspection ##
+
+ def info(self):
+ raise NotImplementedError
+
+ def htmlInfo(self):
+ raise NotImplementedError
diff --git a/paste/webkit/wkresponse.py b/paste/webkit/wkresponse.py
new file mode 100644
index 0000000..0cf0c2a
--- /dev/null
+++ b/paste/webkit/wkresponse.py
@@ -0,0 +1,267 @@
+"""
+A Webware HTTPResponse object.
+"""
+
+import time
+from wkcommon import NoDefault, Cookie
+
+class HTTPResponse(object):
+
+ def __init__(self, transaction, environ, start_response):
+ self._transaction = transaction
+ self._environ = environ
+ self._start_response = start_response
+ self._writer = None
+ self._committed = False
+ self._autoFlush = False
+ self.reset()
+
+ def endTime(self):
+ return self._endTime
+
+ def recordEndTime(self):
+ """
+ Stores the current time as the end time of the response. This
+ should be invoked at the end of deliver(). It may also be
+ invoked by the application for those responses that never
+ deliver due to an error."""
+ self._endTime = time.time()
+
+ ## Headers ##
+
+ def header(self, name, default=NoDefault):
+ """ Returns the value of the specified header. """
+ if default is NoDefault:
+ return self._headers[name.lower()]
+ else:
+ return self._headers.get(name.lower(), default)
+
+ def hasHeader(self, name):
+ return self._headers.has_key(name.lower())
+
+ def setHeader(self, name, value):
+ """
+ Sets a specific header by name.
+ """
+ assert self._committed==0, "Headers have already been sent"
+ self._headers[name.lower()] = value
+
+ def headers(self):
+ """
+ Returns a dictionary-style object of all Header objects
+ contained by this request. """
+ return self._headers
+
+ def clearHeaders(self):
+ """
+ Clears all the headers. You might consider a
+ setHeader('Content-type', 'text/html') or something similar
+ after this."""
+ assert self._committed==0
+ self._headers = {}
+
+ ## Cookies ##
+
+ def cookie(self, name):
+ """ Returns the value of the specified cookie. """
+ return self._cookies[name]
+
+ def hasCookie(self, name):
+ """
+ Returns true if the specified cookie is present.
+ """
+ return self._cookies.has_key(name)
+
+ def setCookie(self, name, value, path='/', expires='ONCLOSE',
+ secure=False):
+ """
+ Set a cookie. You can also set the path (which defaults to /),
+ You can also set when it expires. It can expire:
+ 'NOW': this is the same as trying to delete it, but it
+ doesn't really seem to work in IE
+ 'ONCLOSE': the default behavior for cookies (expires when
+ the browser closes)
+ 'NEVER': some time in the far, far future.
+ integer: a timestamp value
+ tuple: a tuple, as created by the time module
+ """
+ cookie = Cookie(name, value)
+ if expires == 'ONCLOSE' or not expires:
+ pass # this is already default behavior
+ elif expires == 'NOW' or expires == 'NEVER':
+ t = time.gmtime(time.time())
+ if expires == 'NEVER':
+ t = (t[0] + 10,) + t[1:]
+ t = time.strftime("%a, %d-%b-%Y %H:%M:%S GMT", t)
+ cookie.setExpires(t)
+ else:
+ t = expires
+ if type(t) is StringType and t and t[0] == '+':
+ interval = timeDecode(t[1:])
+ t = time.time() + interval
+ if type(t) in (IntType, LongType,FloatType):
+ t = time.gmtime(t)
+ if type(t) in (TupleType, TimeTupleType):
+ t = time.strftime("%a, %d-%b-%Y %H:%M:%S GMT", t)
+ cookie.setExpires(t)
+ if path:
+ cookie.setPath(path)
+ if secure:
+ cookie.setSecure(secure)
+ self.addCookie(cookie)
+
+ def addCookie(self, cookie):
+ """
+ Adds a cookie that will be sent with this response.
+ cookie is a Cookie object instance. See WebKit.Cookie.
+ """
+ assert self._committed==0
+ assert isinstance(cookie, Cookie)
+ self._cookies[cookie.name()] = cookie
+
+ def delCookie(self, name):
+ """
+ Deletes a cookie at the browser. To do so, one has
+ to create and send to the browser a cookie with
+ parameters that will cause the browser to delete it.
+ """
+ if self._cookies.has_key(name):
+ self._cookies[name].delete()
+ else:
+ cookie = Cookie(name, None)
+ cookie.delete()
+ self.addCookie(cookie)
+
+ def cookies(self):
+ """
+ Returns a dictionary-style object of all Cookie objects that will be sent
+ with this response.
+ """
+ return self._cookies
+
+ def clearCookies(self):
+ """ Clears all the cookies. """
+ assert self._committed==0
+ self._cookies = {}
+
+ ## Status ##
+
+ def setStatus(self, code, msg=''):
+ """ Set the status code of the response, such as 200, 'OK'. """
+ assert self._committed==0, "Headers already sent."
+ self.setHeader('Status', str(code) + ' ' + msg)
+
+ ## Special responses ##
+
+ def sendError(self, code, msg=''):
+ """
+ Sets the status code to the specified code and message.
+ """
+ assert self._committed==0, "Response already partially sent"
+ self.setStatus(code, msg)
+
+ def sendRedirect(self, url):
+ """
+ This method sets the headers and content for the redirect, but
+ does NOT change the cookies. Use clearCookies() as
+ appropriate.
+
+ @@ 2002-03-21 ce: I thought cookies were ignored by user
+ agents if a redirect occurred. We should verify and update
+ code or docs as appropriate.
+ """
+ # ftp://ftp.isi.edu/in-notes/rfc2616.txt
+ # Sections: 10.3.3 and others
+
+ assert self._committed==0, "Headers already sent"
+
+ self.setHeader('Status', '302 Redirect')
+ self.setHeader('Location', url)
+ self.setHeader('Content-type', 'text/html')
+
+ self.write('<html> <body> This page has been redirected to '
+ '<a href="%s">%s</a>. </body> </html>' % (url, url))
+
+ ## Output ##
+
+ def write(self, charstr=None):
+ """
+ Write charstr to the response stream.
+ """
+ import pdb
+ if not charstr:
+ return
+ if self._autoFlush:
+ assert self._committed
+ self._writer(charstr)
+ else:
+ self._output.append(charstr)
+
+ def flush(self, autoFlush=True):
+ """
+ Send all accumulated response data now. Commits the response
+ headers and tells the underlying stream to flush. if
+ autoFlush is true, the responseStream will flush itself
+ automatically from now on.
+ """
+ if not self._committed:
+ self.commit()
+ if self._output:
+ self._writer(''.join(self._output))
+ self._autoFlush = autoFlush
+
+ def isCommitted(self):
+ """
+ Has the reponse already been partially or completely sent? If
+ this returns true, no new headers/cookies can be added to the
+ response.
+ """
+ return self._committed
+
+ def deliver(self):
+ """
+ The final step in the processing cycle.
+ Not used for much with responseStreams added.
+ """
+ self.recordEndTime()
+ if not self._committed: self.commit()
+
+ def commit(self):
+ """
+ Write out all headers to the reponse stream, and tell the
+ underlying response stream it can start sending data.
+ """
+ status = self._headers['status']
+ del self._headers['status']
+ headers = self._headers.items()
+ for cookie in self._cookies.values():
+ headers.append(('Set-Cookie', cookie.headerValue()))
+ self._writer = self._start_response(status, headers)
+ self._committed = True
+
+ def wsgiIterator(self):
+ return self._output
+
+ def recordSession(self):
+ raise NotImplementedError
+
+ def reset(self):
+ """
+ Resets the response (such as headers, cookies and contents).
+ """
+
+ assert self._committed == 0
+ self._headers = {}
+ self.setHeader('Content-type','text/html')
+ self.setHeader('Status', '200 OK')
+ self._cookies = {}
+ self._output = []
+
+ def rawResponse(self):
+ raise NotImplementedError
+
+ def size(self):
+ raise NotImplementedError
+
+ def mergeTextHeaders(self, headerstr):
+ raise NotImplementedError
diff --git a/paste/webkit/wkservlet.py b/paste/webkit/wkservlet.py
new file mode 100644
index 0000000..2e37dba
--- /dev/null
+++ b/paste/webkit/wkservlet.py
@@ -0,0 +1,430 @@
+"""
+This implements all of the Webware servlets (Servlet, HTTPServlet, and
+Page), as WSGI applications. The servlets themselves are
+applications, and __call__ is provided to do this.
+"""
+
+import wkcommon
+from wktransaction import Transaction
+
+class Servlet(object):
+
+ # This is nested in Servlet so that transactions can access it as
+ # an attribute, instead of having to import this module. (If they
+ # had to import this module, there would be a circular import)
+ # @@: Why not just put this in wktransaction?
+ class ReturnIterException(Exception):
+ def __init__(self, app_iter):
+ self.app_iter = app_iter
+
+ def __call__(self, environ, start_response):
+ """
+ The core WSGI method, and the core of the servlet execution.
+ """
+ trans = Transaction(environ, start_response)
+ trans.setServlet(self)
+ try:
+ trans.runTransaction()
+ trans.response().deliver()
+ return trans.response().wsgiIterator()
+ except self.ReturnIterException, e:
+ return e.app_iter
+ except self.EndResponse:
+ trans.response().deliver()
+ return trans.response().wsgiIterator()
+
+ ## Access ##
+
+ def name(self):
+ """
+ Returns the name which is simple the name of the
+ class. Subclasses should *not* override this method. It is
+ used for logging and debugging. """
+ return self.__class__.__name__
+
+
+ def awake(self, trans):
+ """
+ This message is sent to all objects that participate in the
+ request-response cycle in a top-down fashion, prior to
+ respond(). Subclasses must invoke super.
+ """
+ self._transaction = trans
+
+ def respond(self, trans):
+ raise AbstractError, self.__class__
+
+ def sleep(self, trans):
+ pass
+
+ ## Abilities ##
+
+ def canBeThreaded(self):
+ """ Returns 0 or 1 to indicate if the servlet can be
+ multithreaded. This value should not change during the
+ lifetime of the object. The default implementation returns
+ 0. Note: This is not currently used. """
+ return 0
+
+ def canBeReused(self):
+ """ Returns 0 or 1 to indicate if a single servlet instance
+ can be reused. The default is 1, but subclasses con override
+ to return 0. Keep in mind that performance may seriously be
+ degraded if instances can't be reused. Also, there's no known
+ good reasons not to reuse and instance. Remember the awake()
+ and sleep() methods are invoked for every transaction. But
+ just in case, your servlet can refuse to be reused. """
+ return 1
+
+class HTTPServlet(Servlet):
+
+ def __init__(self):
+ Servlet.__init__(self)
+ self._methodForRequestType = {} # a cache; see respond()
+
+ ## From WebKit.HTTPServlet ##
+
+ def respond(self, trans):
+ """
+ Invokes the appropriate respondToSomething() method
+ depending on the type of request (e.g., GET, POST, PUT,
+ ...). """
+ httpMethodName = trans.request().method()
+ method = self._methodForRequestType.get(httpMethodName, None)
+ if not method:
+ methName = 'respondTo' + httpMethodName.capitalize()
+ method = getattr(self, methName, self.notImplemented)
+ self._methodForRequestType[httpMethodName] = method
+ method(trans)
+
+ def notImplemented(self, trans):
+ trans.response().setHeader('Status', '501 Not Implemented')
+
+ def respondToHead(self, trans):
+ """
+ A correct but inefficient implementation.
+ Should at least provide Last-Modified and Content-Length.
+ """
+ res = trans.response()
+ w = res.write
+ res.write = lambda *args: None
+ self.respondToGet(trans)
+ res.write = w
+
+class Page(HTTPServlet):
+
+ class EndResponse(Exception):
+ pass
+
+ ## Server side filesystem ##
+
+ def serverSidePath(self, path=None):
+ raise NotImplementedError
+
+ ## From WebKit.Page ##
+
+ def awake(self, transaction):
+ self._transaction = transaction
+ self._response = transaction.response()
+ self._request = transaction.request()
+ self._session = None # don't create unless needed
+ assert self._transaction is not None
+ assert self._response is not None
+ assert self._request is not None
+
+ def respondToGet(self, transaction):
+ """ Invokes _respond() to handle the transaction. """
+ self._respond(transaction)
+
+ def respondToPost(self, transaction):
+ """ Invokes _respond() to handle the transaction. """
+ self._respond(transaction)
+
+ def _respond(self, transaction):
+ """
+ Handles actions if an _action_ field is defined, otherwise
+ invokes writeHTML().
+ Invoked by both respondToGet() and respondToPost().
+ """
+ req = transaction.request()
+
+ # Check for actions
+ for action in self.actions():
+ if req.hasField('_action_%s' % action) or \
+ req.field('_action_', None) == action or \
+ (req.hasField('_action_%s.x' % action) and \
+ req.hasField('_action_%s.y' % action)):
+ if self._actionSet().has_key(action):
+ self.handleAction(action)
+ return
+
+ self.writeHTML()
+
+ def sleep(self, transaction):
+ self._session = None
+ self._request = None
+ self._response = None
+ self._transaction = None
+
+ ## Access ##
+
+ def application(self):
+ return self.transaction().application()
+
+ def transaction(self):
+ return self._transaction
+
+ def request(self):
+ return self._request
+
+ def response(self):
+ return self._response
+
+ def session(self):
+ if not self._session:
+ self._session = self._transaction.session()
+ return self._session
+
+
+ ## Generating results ##
+
+ def title(self):
+ """ Subclasses often override this method to provide a custom title. This title should be absent of HTML tags. This implementation returns the name of the class, which is sometimes appropriate and at least informative. """
+ return self.__class__.__name__
+
+ def htTitle(self):
+ """ Return self.title(). Subclasses sometimes override this to provide an HTML enhanced version of the title. This is the method that should be used when including the page title in the actual page contents. """
+ return self.title()
+
+ def htBodyArgs(self):
+ """
+ Returns the arguments used for the HTML <body> tag. Invoked by
+ writeBody().
+
+ With the prevalence of stylesheets (CSS), you can probably skip
+ this particular HTML feature.
+ """
+ return 'color=black bgcolor=white'
+
+ def writeHTML(self):
+ """
+ Writes all the HTML for the page.
+
+ Subclasses may override this method (which is invoked by
+ respondToGet() and respondToPost()) or more commonly its
+ constituent methods, writeDocType(), writeHead() and
+ writeBody().
+ """
+ self.writeDocType()
+ self.writeln('<html>')
+ self.writeHead()
+ self.writeBody()
+ self.writeln('</html>')
+
+ def writeDocType(self):
+ """
+ Invoked by writeHTML() to write the <!DOCTYPE ...> tag.
+
+ @@ sgd-2003-01-29 - restored the 4.01 transitional as per discussions
+ on the mailing list for the 0.8 release.
+
+ # This implementation USED TO specify HTML 4.01 Transitional, but
+ # some versions of Mozilla acted strangely with that. The current
+ # implementation does nothing.
+
+ Subclasses may override to specify something else.
+
+ You can find out more about doc types by searching for DOCTYPE
+ on the web, or visiting:
+ http://www.htmlhelp.com/tools/validator/doctype.html
+ """
+ self.writeln('<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">')
+ pass
+
+ def writeHead(self):
+ """
+ Writes the <head> portion of the page by writing the
+ <head>...</head> tags and invoking writeHeadParts() in between.
+ """
+ wr = self.writeln
+ wr('<head>')
+ self.writeHeadParts()
+ wr('</head>')
+
+ def writeHeadParts(self):
+ """
+ Writes the parts inside the <head>...</head> tags. Invokes
+ writeTitle() and writeStyleSheet(). Subclasses can override this
+ to add additional items and should invoke super.
+ """
+ self.writeTitle()
+ self.writeStyleSheet()
+
+ def writeTitle(self):
+ """
+ Writes the <title> portion of the page. Uses title().
+ """
+ self.writeln('\t<title>%s</title>' % self.title())
+
+ def writeStyleSheet(self):
+ """
+ Writes the style sheet for the page, however, this default
+ implementation does nothing. Subclasses should override if
+ necessary. A typical implementation is:
+ self.writeln('\t<link rel=stylesheet href=StyleSheet.css type=text/css>')
+ """
+ pass
+
+ def writeBody(self):
+ """
+ Writes the <body> portion of the page by writing the
+ <body>...</body> (making use of self.htBodyArgs()) and invoking
+ self.writeBodyParts() in between.
+ """
+ wr = self.writeln
+ bodyArgs = self.htBodyArgs()
+ if bodyArgs:
+ wr('<body %s>' % bodyArgs)
+ else:
+ wr('<body>')
+ self.writeBodyParts()
+ wr('</body>')
+
+ def writeBodyParts(self):
+ """
+ Invokes writeContent(). Subclasses should only override this
+ method to provide additional page parts such as a header,
+ sidebar and footer, that a subclass doesn't normally have to
+ worry about writing.
+
+ For writing page-specific content, subclasses should override
+ writeContent() instead.
+
+ See SidebarPage for an example override of this method.
+
+ Invoked by writeBody().
+ """
+ self.writeContent()
+
+ def writeContent(self):
+ """
+ Writes the unique, central content for the page.
+
+ Subclasses should override this method (not invoking super) to
+ write their unique page content.
+
+ Invoked by writeBodyParts().
+ """
+ self.writeln('<p> This page has not yet customized its content. </p>')
+
+
+ ## Writing ##
+
+ def write(self, *args):
+ for arg in args:
+ self._response.write(str(arg))
+
+ def writeln(self, *args):
+ for arg in args:
+ self._response.write(str(arg))
+ self._response.write('\n')
+
+
+ ## Threading ##
+
+ def canBeThreaded(self):
+ """ Returns 0 because of the ivars we set up in awake(). """
+ return 0
+
+
+ ## Actions ##
+
+ def handleAction(self, action):
+ """
+ Invoked by `_respond` when a legitimate action has
+ been found in a form. Invokes `preAction`, the actual
+ action method and `postAction`.
+
+ Subclasses rarely override this method.
+ """
+ self.preAction(action)
+ getattr(self, action)()
+ self.postAction(action)
+
+ def actions(self):
+ return []
+
+ def preAction(self, actionName):
+ raise NotImplementedError
+
+ def postAction(self, actionName):
+ raise NotImplementedError
+
+ def methodNameForAction(self, name):
+ raise NotImplementedError
+
+ ## Convenience ##
+
+ def htmlEncode(self, s):
+ return wkcommon.htmlEncode(s)
+
+ def htmlDecode(self, s):
+ return wkcommon.htmlDecode(s)
+
+ def urlEncode(self, s):
+ return wkcommon.urlEncode(s)
+
+ def urlDecode(self, s):
+ return wkcommon.urlDecode(s)
+
+ def forward(self, URL):
+ self.application().forward(self.transaction(), URL)
+
+ def includeURL(self, URL):
+ raise NotImplementedError
+
+ def callMethodOfServlet(self, URL, method, *args, **kwargs):
+ raise NotImplementedError
+
+ def endResponse(self):
+ raise self.EndResponse()
+
+ def sendRedirectAndEnd(self, url):
+ """
+ Sends a redirect back to the client and ends the response. This
+ is a very popular pattern.
+ """
+ self.response().sendRedirect(url)
+ self.endResponse()
+
+
+ ## Self utility ##
+
+ def sessionEncode(self, url=None):
+ """
+ Utility function to access session.sessionEncode.
+ Takes a url and adds the session ID as a parameter. This is for cases where
+ you don't know if the client will accepts cookies.
+ """
+ if url == None:
+ url = self.request().uri()
+ return self.session().sessionEncode(url)
+
+
+ ## Private utility ##
+
+ def _actionSet(self):
+ """ Returns a dictionary whose keys are the names returned by actions(). The dictionary is used for a quick set-membership-test in self._respond. Subclasses don't generally override this method or invoke it. """
+ if not hasattr(self, '_actionDict'):
+ self._actionDict = {}
+ for action in self.actions():
+ self._actionDict[action] = 1
+ return self._actionDict
+
+
+ ## Validate HTML output (developer debugging) ##
+
+ def validateHTML(self, closingTags='</body></html>'):
+ raise NotImplementedError
+
+
diff --git a/paste/webkit/wksession.py b/paste/webkit/wksession.py
new file mode 100644
index 0000000..1cbdd71
--- /dev/null
+++ b/paste/webkit/wksession.py
@@ -0,0 +1,47 @@
+"""
+The WebKit session object; an interface surrounding a persistent
+dictionary.
+"""
+
+from wkcommon import NoDefault
+
+class Session:
+
+ def __init__(self, dict):
+ self._values = dict
+
+ def invalidate(self):
+ self._values.clear()
+
+ def value(self, name, default=NoDefault):
+ if default is NoDefault:
+ return self._values[name]
+ else:
+ return self._values.get(name, default)
+
+ def hasValue(self, name):
+ return self._values.has_key(name)
+
+ def setValue(self, name, value):
+ self._values[name] = value
+
+ def delValue(self, name):
+ del self._values[name]
+
+ def values(self):
+ return self._values
+
+ def setTimeout(self, timeout):
+ # @@: This should really do something
+ pass
+
+ def __getitem__(self, name):
+ return self.value(name)
+
+ def __setitem__(self, name, value):
+ self.setValue(name, value)
+
+ def __delitem__(self, name):
+ self.delValue(name)
+
+
diff --git a/paste/webkit/wktransaction.py b/paste/webkit/wktransaction.py
new file mode 100644
index 0000000..af73531
--- /dev/null
+++ b/paste/webkit/wktransaction.py
@@ -0,0 +1,96 @@
+"""
+The Webware transaction object. Responsible for creating the request
+and response objects, and managing some parts of the request cycle.
+"""
+
+from wkrequest import HTTPRequest
+from wkresponse import HTTPResponse
+from wksession import Session
+from wkapplication import Application
+
+class Transaction(object):
+
+ def __init__(self, environ, start_response):
+ self._environ = environ
+ self._start_response = start_response
+ self._request = HTTPRequest(self, environ)
+ self._response = HTTPResponse(self, environ, start_response)
+ self._session = None
+ self._application = None
+
+ def application(self):
+ if self._application is None:
+ self._application = Application(self)
+ return self._application
+
+ def request(self):
+ return self._request
+
+ def response(self):
+ return self._response
+
+ def setResponse(self, response):
+ assert 0, "The response cannot be set"
+
+ def hasSession(self):
+ return self._session is not None
+
+ def session(self):
+ if not self._session:
+ self._session = Session(self.request().environ()['paste.session.factory']())
+ return self._session
+
+ def setSession(self, session):
+ self._session = session
+
+ def servlet(self):
+ return self._servlet
+
+ def setServlet(self, servlet):
+ self._servlet = servlet
+
+ def duration(self):
+ return self.response().endTime() - self.request().time()
+
+ def errorOccurred(self):
+ assert 0, "Not tracked"
+
+ def setErrorOccurred(self, flag):
+ assert 0, "Not tracked"
+
+ def awake(self):
+ if self._session:
+ self._session.awake(self)
+ self._servlet.awake(self)
+
+ def respond(self):
+ self._servlet.respond(self)
+
+ def sleep(self):
+ self._servlet.sleep(self)
+
+ def die(self):
+ # In WebKit this looks for any instance variables with a
+ # resetKeyBindings method, but I'm not sure why
+ pass
+
+ def writeExceptionReport(self, handler):
+ assert 0, "Not implemented"
+
+ def runTransaction(self):
+ try:
+ self.awake()
+ self.respond()
+ finally:
+ self.sleep()
+
+ def forward(self, url):
+ assert self._environ.has_key('paste.recursive.forward'), \
+ "Forwarding is not supported (use the recursive middleware)"
+ if url.startswith('/'):
+ # Webware considers absolute paths to still be based off
+ # of the Webware root; but recursive does not.
+ url = url[1:]
+ app_iter = self._environ['paste.recursive.forward'](url)
+ raise self._servlet.ReturnIterException(app_iter)
+
diff --git a/paste/webkit/wsgiwebkit.py b/paste/webkit/wsgiwebkit.py
new file mode 100644
index 0000000..7989702
--- /dev/null
+++ b/paste/webkit/wsgiwebkit.py
@@ -0,0 +1,67 @@
+"""
+wkserver constructs the WSGI stack of middleware to serve WebKit
+content. It actually does not include a server (umm... bad name;
+"application" was taken).
+
+Use ``some_server(webkit('/path/to/servlets/'))``
+"""
+
+import sys
+import os
+from paste import urlparser
+from paste import session
+from paste import recursive
+from paste import httpexceptions
+from paste import lint
+from paste import error_middleware
+
+def webkit(directory, install_fake_webware=True, use_lint=False):
+ if install_fake_webware:
+ _install_fake_webware()
+ app = urlparser.URLParser(directory, os.path.basename(directory))
+ if use_lint:
+ app = lint.middleware(app)
+ app = session.SessionMiddleware(app)
+ if use_lint:
+ app = lint.middleware(app)
+ app = httpexceptions.middleware(app)
+ if use_lint:
+ app = lint.middleware(app)
+ app = recursive.RecursiveMiddleware(app)
+ if use_lint:
+ app = lint.middleware(app)
+ app = error_middleware.ErrorMiddleware(app)
+ # I'll skip the use of lint on recursive, because it doesn't modify
+ # its output much at all
+ return app
+
+def install_fake_webware():
+ fake_webware_dir = os.path.join(os.path.dirname(__file__),
+ 'FakeWebware')
+ if fake_webware_dir in sys.path:
+ return
+ sys.path.insert(0, fake_webware_dir)
+_install_fake_webware = install_fake_webware
+
+if __name__ == '__main__':
+ if os.environ.has_key('SERVER_NAME'):
+ import cgiserver
+ cgiserver.run_with_cgi(webkit(os.path.dirname(__file__)),
+ use_cgitb=True,
+ redirect_stdout=True)
+ else:
+ import wsgilib
+ import sys
+ import os
+ if '-h' in sys.argv or '--help' in sys.argv or not sys.argv[1:]:
+ print 'Usage: %s URL_PATH' % sys.argv[0]
+ print 'Run like: CLIENT_DIR=XXX %s URL_PATH' % sys.argv[0]
+ print 'to put the root of the URL_PATH in in XXX'
+ sys.exit()
+ root = os.environ.get('CLIENT_DIR', os.path.dirname(__file__))
+ application = webkit(root)
+ def prapp(url, app=None):
+ if app is None:
+ app = application
+ print wsgilib.interactive(app, url)
+ prapp(sys.argv[1])