diff --git a/others/SOAP.py b/others/SOAP.py index bb7baf5ee..a7cd085d8 100644 --- a/others/SOAP.py +++ b/others/SOAP.py @@ -1,3978 +1,3978 @@ -#!/usr/bin/python -################################################################################ -# -# SOAP.py 0.9.7 - Cayce Ullman (cayce@actzero.com) -# Brian Matthews (blm@actzero.com) -# -# INCLUDED: -# - General SOAP Parser based on sax.xml (requires Python 2.0) -# - General SOAP Builder -# - SOAP Proxy for RPC client code -# - SOAP Server framework for RPC server code -# -# FEATURES: -# - Handles all of the types in the BDG -# - Handles faults -# - Allows namespace specification -# - Allows SOAPAction specification -# - Homogeneous typed arrays -# - Supports multiple schemas -# - Header support (mustUnderstand and actor) -# - XML attribute support -# - Multi-referencing support (Parser/Builder) -# - Understands SOAP-ENC:root attribute -# - Good interop, passes all client tests for Frontier, SOAP::LITE, SOAPRMI -# - Encodings -# - SSL clients (with OpenSSL configured in to Python) -# - SSL servers (with OpenSSL configured in to Python and M2Crypto installed) -# -# TODO: -# - Timeout on method calls - MCU -# - Arrays (sparse, multidimensional and partial) - BLM -# - Clean up data types - BLM -# - Type coercion system (Builder) - MCU -# - Early WSDL Support - MCU -# - Attachments - BLM -# - setup.py - MCU -# - mod_python example - MCU -# - medusa example - MCU -# - Documentation - JAG -# - Look at performance -# -################################################################################ -# -# Copyright (c) 2001, Cayce Ullman. -# Copyright (c) 2001, Brian Matthews. -# -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# Neither the name of actzero, inc. nor the names of its contributors may -# be used to endorse or promote products derived from this software without -# specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR -# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# -################################################################################ -# -# Additional changes: -# 0.9.7.3 - 4/18/2002 - Mark Pilgrim (f8dy@diveintomark.org) -# added dump_dict as alias for dump_dictionary for Python 2.2 compatibility -# 0.9.7.2 - 4/12/2002 - Mark Pilgrim (f8dy@diveintomark.org) -# fixed logic to unmarshal the value of "null" attributes ("true" or "1" -# means true, others false) -# 0.9.7.1 - 4/11/2002 - Mark Pilgrim (f8dy@diveintomark.org) -# added "dump_str" as alias for "dump_string" for Python 2.2 compatibility -# Between 2.1 and 2.2, type("").__name__ changed from "string" to "str" -################################################################################ - -import xml.sax -import UserList -import base64 -import cgi -import urllib -import exceptions -import copy -import re -import socket -import string -import sys -import time -import SocketServer -from types import * - -try: from M2Crypto import SSL -except: pass - -ident = '$Id$' - -__version__ = "0.9.7.3" - -# Platform hackery - -# Check float support -try: - float("NaN") - float("INF") - float("-INF") - good_float = 1 -except: - good_float = 0 - -################################################################################ -# Exceptions -################################################################################ -class Error(exceptions.Exception): - def __init__(self, msg): - self.msg = msg - def __str__(self): - return "" % self.msg - __repr__ = __str__ - -class RecursionError(Error): - pass - -class UnknownTypeError(Error): - pass - -class HTTPError(Error): - # indicates an HTTP protocol error - def __init__(self, code, msg): - self.code = code - self.msg = msg - def __str__(self): - return "" % (self.code, self.msg) - __repr__ = __str__ - -############################################################################## -# Namespace Class -################################################################################ -def invertDict(dict): - d = {} - - for k, v in dict.items(): - d[v] = k - - return d - -class NS: - XML = "http://www.w3.org/XML/1998/namespace" - - ENV = "http://schemas.xmlsoap.org/soap/envelope/" - ENC = "http://schemas.xmlsoap.org/soap/encoding/" - - XSD = "http://www.w3.org/1999/XMLSchema" - XSD2 = "http://www.w3.org/2000/10/XMLSchema" - XSD3 = "http://www.w3.org/2001/XMLSchema" - - XSD_L = [XSD, XSD2, XSD3] - EXSD_L= [ENC, XSD, XSD2, XSD3] - - XSI = "http://www.w3.org/1999/XMLSchema-instance" - XSI2 = "http://www.w3.org/2000/10/XMLSchema-instance" - XSI3 = "http://www.w3.org/2001/XMLSchema-instance" - XSI_L = [XSI, XSI2, XSI3] - - URN = "http://soapinterop.org/xsd" - - # For generated messages - XML_T = "xml" - ENV_T = "SOAP-ENV" - ENC_T = "SOAP-ENC" - XSD_T = "xsd" - XSD2_T= "xsd2" - XSD3_T= "xsd3" - XSI_T = "xsi" - XSI2_T= "xsi2" - XSI3_T= "xsi3" - URN_T = "urn" - - NSMAP = {ENV_T: ENV, ENC_T: ENC, XSD_T: XSD, XSD2_T: XSD2, - XSD3_T: XSD3, XSI_T: XSI, XSI2_T: XSI2, XSI3_T: XSI3, - URN_T: URN} - NSMAP_R = invertDict(NSMAP) - - STMAP = {'1999': (XSD_T, XSI_T), '2000': (XSD2_T, XSI2_T), - '2001': (XSD3_T, XSI3_T)} - STMAP_R = invertDict(STMAP) - - def __init__(self): - raise Error, "Don't instantiate this" - -################################################################################ -# Configuration class -################################################################################ - -class SOAPConfig: - __readonly = ('SSLserver', 'SSLclient') - - def __init__(self, config = None, **kw): - d = self.__dict__ - - if config: - if not isinstance(config, SOAPConfig): - raise AttributeError, \ - "initializer must be SOAPConfig instance" - - s = config.__dict__ - - for k, v in s.items(): - if k[0] != '_': - d[k] = v - else: - # Setting debug also sets returnFaultInfo, dumpFaultInfo, - # dumpHeadersIn, dumpHeadersOut, dumpSOAPIn, and dumpSOAPOut - self.debug = 0 - # Setting namespaceStyle sets typesNamespace, typesNamespaceURI, - # schemaNamespace, and schemaNamespaceURI - self.namespaceStyle = '1999' - self.strictNamespaces = 0 - self.typed = 1 - self.buildWithNamespacePrefix = 1 - self.returnAllAttrs = 0 - - try: SSL; d['SSLserver'] = 1 - except: d['SSLserver'] = 0 - - try: socket.ssl; d['SSLclient'] = 1 - except: d['SSLclient'] = 0 - - for k, v in kw.items(): - if k[0] != '_': - setattr(self, k, v) - - def __setattr__(self, name, value): - if name in self.__readonly: - raise AttributeError, "readonly configuration setting" - - d = self.__dict__ - - if name in ('typesNamespace', 'typesNamespaceURI', - 'schemaNamespace', 'schemaNamespaceURI'): - - if name[-3:] == 'URI': - base, uri = name[:-3], 1 - else: - base, uri = name, 0 - - if type(value) == StringType: - if NS.NSMAP.has_key(value): - n = (value, NS.NSMAP[value]) - elif NS.NSMAP_R.has_key(value): - n = (NS.NSMAP_R[value], value) - else: - raise AttributeError, "unknown namespace" - elif type(value) in (ListType, TupleType): - if uri: - n = (value[1], value[0]) - else: - n = (value[0], value[1]) - else: - raise AttributeError, "unknown namespace type" - - d[base], d[base + 'URI'] = n - - try: - d['namespaceStyle'] = \ - NS.STMAP_R[(d['typesNamespace'], d['schemaNamespace'])] - except: - d['namespaceStyle'] = '' - - elif name == 'namespaceStyle': - value = str(value) - - if not NS.STMAP.has_key(value): - raise AttributeError, "unknown namespace style" - - d[name] = value - n = d['typesNamespace'] = NS.STMAP[value][0] - d['typesNamespaceURI'] = NS.NSMAP[n] - n = d['schemaNamespace'] = NS.STMAP[value][1] - d['schemaNamespaceURI'] = NS.NSMAP[n] - - elif name == 'debug': - d[name] = \ - d['returnFaultInfo'] = \ - d['dumpFaultInfo'] = \ - d['dumpHeadersIn'] = \ - d['dumpHeadersOut'] = \ - d['dumpSOAPIn'] = \ - d['dumpSOAPOut'] = value - - else: - d[name] = value - -Config = SOAPConfig() - -################################################################################ -# Types and Wrappers -################################################################################ - -class anyType: - _validURIs = (NS.XSD, NS.XSD2, NS.XSD3, NS.ENC) - - def __init__(self, data = None, name = None, typed = 1, attrs = None): - if self.__class__ == anyType: - raise Error, "anyType can't be instantiated directly" - - if type(name) in (ListType, TupleType): - self._ns, self._name = name - else: - self._ns, self._name = self._validURIs[0], name - self._typed = typed - self._attrs = {} - - self._cache = None - self._type = self._typeName() - - self._data = self._checkValueSpace(data) - - if attrs != None: - self._setAttrs(attrs) - - def __str__(self): - if self._name: - return "<%s %s at %d>" % (self.__class__, self._name, id(self)) - return "<%s at %d>" % (self.__class__, id(self)) - - __repr__ = __str__ - - def _checkValueSpace(self, data): - return data - - def _marshalData(self): - return str(self._data) - - def _marshalAttrs(self, ns_map, builder): - a = '' - - for attr, value in self._attrs.items(): - ns, n = builder.genns(ns_map, attr[0]) - a += n + ' %s%s="%s"' % \ - (ns, attr[1], cgi.escape(str(value), 1)) - - return a - - def _fixAttr(self, attr): - if type(attr) in (StringType, UnicodeType): - attr = (None, attr) - elif type(attr) == ListType: - attr = tuple(attr) - elif type(attr) != TupleType: - raise AttributeError, "invalid attribute type" - - if len(attr) != 2: - raise AttributeError, "invalid attribute length" - - if type(attr[0]) not in (NoneType, StringType, UnicodeType): - raise AttributeError, "invalid attribute namespace URI type" - - return attr - - def _getAttr(self, attr): - attr = self._fixAttr(attr) - - try: - return self._attrs[attr] - except: - return None - - def _setAttr(self, attr, value): - attr = self._fixAttr(attr) - - self._attrs[attr] = str(value) - - def _setAttrs(self, attrs): - if type(attrs) in (ListType, TupleType): - for i in range(0, len(attrs), 2): - self._setAttr(attrs[i], attrs[i + 1]) - - return - - if type(attrs) == DictType: - d = attrs - elif isinstance(attrs, anyType): - d = attrs._attrs - else: - raise AttributeError, "invalid attribute type" - - for attr, value in d.items(): - self._setAttr(attr, value) - - def _setMustUnderstand(self, val): - self._setAttr((NS.ENV, "mustUnderstand"), val) - - def _getMustUnderstand(self): - return self._getAttr((NS.ENV, "mustUnderstand")) - - def _setActor(self, val): - self._setAttr((NS.ENV, "actor"), val) - - def _getActor(self): - return self._getAttr((NS.ENV, "actor")) - - def _typeName(self): - return self.__class__.__name__[:-4] - - def _validNamespaceURI(self, URI, strict): - if not self._typed: - return None - if URI in self._validURIs: - return URI - if not strict: - return self._ns - raise AttributeError, \ - "not a valid namespace for type %s" % self._type - -class voidType(anyType): - pass - -class stringType(anyType): - def _checkValueSpace(self, data): - if data == None: - raise ValueError, "must supply initial %s value" % self._type - - if type(data) not in (StringType, UnicodeType): - raise AttributeError, "invalid %s type" % self._type - - return data - -class untypedType(stringType): - def __init__(self, data = None, name = None, attrs = None): - stringType.__init__(self, data, name, 0, attrs) - -class IDType(stringType): pass -class NCNameType(stringType): pass -class NameType(stringType): pass -class ENTITYType(stringType): pass -class IDREFType(stringType): pass -class languageType(stringType): pass -class NMTOKENType(stringType): pass -class QNameType(stringType): pass - -class tokenType(anyType): - _validURIs = (NS.XSD2, NS.XSD3) - __invalidre = '[\n\t]|^ | $| ' - - def _checkValueSpace(self, data): - if data == None: - raise ValueError, "must supply initial %s value" % self._type - - if type(data) not in (StringType, UnicodeType): - raise AttributeError, "invalid %s type" % self._type - - if type(self.__invalidre) == StringType: - self.__invalidre = re.compile(self.__invalidre) - - if self.__invalidre.search(data): - raise ValueError, "invalid %s value" % self._type - - return data - -class normalizedStringType(anyType): - _validURIs = (NS.XSD3,) - __invalidre = '[\n\r\t]' - - def _checkValueSpace(self, data): - if data == None: - raise ValueError, "must supply initial %s value" % self._type - - if type(data) not in (StringType, UnicodeType): - raise AttributeError, "invalid %s type" % self._type - - if type(self.__invalidre) == StringType: - self.__invalidre = re.compile(self.__invalidre) - - if self.__invalidre.search(data): - raise ValueError, "invalid %s value" % self._type - - return data - -class CDATAType(normalizedStringType): - _validURIs = (NS.XSD2,) - -class booleanType(anyType): - def __int__(self): - return self._data - - __nonzero__ = __int__ - - def _marshalData(self): - return ['false', 'true'][self._data] - - def _checkValueSpace(self, data): - if data == None: - raise ValueError, "must supply initial %s value" % self._type - - if data in (0, '0', 'false', ''): - return 0 - if data in (1, '1', 'true'): - return 1 - raise ValueError, "invalid %s value" % self._type - -class decimalType(anyType): - def _checkValueSpace(self, data): - if data == None: - raise ValueError, "must supply initial %s value" % self._type - - if type(data) not in (IntType, LongType, FloatType): - raise Error, "invalid %s value" % self._type - - return data - -class floatType(anyType): - def _checkValueSpace(self, data): - if data == None: - raise ValueError, "must supply initial %s value" % self._type - - if type(data) not in (IntType, LongType, FloatType) or \ - data < -3.4028234663852886E+38 or \ - data > 3.4028234663852886E+38: - raise ValueError, "invalid %s value" % self._type - - return data - - def _marshalData(self): - return "%.18g" % self._data # More precision - -class doubleType(anyType): - def _checkValueSpace(self, data): - if data == None: - raise ValueError, "must supply initial %s value" % self._type - - if type(data) not in (IntType, LongType, FloatType) or \ - data < -1.7976931348623158E+308 or \ - data > 1.7976931348623157E+308: - raise ValueError, "invalid %s value" % self._type - - return data - - def _marshalData(self): - return "%.18g" % self._data # More precision - -class durationType(anyType): - _validURIs = (NS.XSD3,) - - def _checkValueSpace(self, data): - if data == None: - raise ValueError, "must supply initial %s value" % self._type - - try: - # A tuple or a scalar is OK, but make them into a list - - if type(data) == TupleType: - data = list(data) - elif type(data) != ListType: - data = [data] - - if len(data) > 6: - raise Exception, "too many values" - - # Now check the types of all the components, and find - # the first nonzero element along the way. - - f = -1 - - for i in range(len(data)): - if data[i] == None: - data[i] = 0 - continue - - if type(data[i]) not in \ - (IntType, LongType, FloatType): - raise Exception, "element %d a bad type" % i - - if data[i] and f == -1: - f = i - - # If they're all 0, just use zero seconds. - - if f == -1: - self._cache = 'PT0S' - - return (0,) * 6 - - # Make sure only the last nonzero element has a decimal fraction - # and only the first element is negative. - - d = -1 - - for i in range(f, len(data)): - if data[i]: - if d != -1: - raise Exception, \ - "all except the last nonzero element must be " \ - "integers" - if data[i] < 0 and i > f: - raise Exception, \ - "only the first nonzero element can be negative" - elif data[i] != long(data[i]): - d = i - - # Pad the list on the left if necessary. - - if len(data) < 6: - n = 6 - len(data) - f += n - d += n - data = [0] * n + data - - # Save index of the first nonzero element and the decimal - # element for _marshalData. - - self.__firstnonzero = f - self.__decimal = d - - except Exception, e: - raise ValueError, "invalid %s value - %s" % (self._type, e) - - return tuple(data) - - def _marshalData(self): - if self._cache == None: - d = self._data - t = 0 - - if d[self.__firstnonzero] < 0: - s = '-P' - else: - s = 'P' - - t = 0 - - for i in range(self.__firstnonzero, len(d)): - if d[i]: - if i > 2 and not t: - s += 'T' - t = 1 - if self.__decimal == i: - s += "%g" % abs(d[i]) - else: - s += "%d" % long(abs(d[i])) - s += ['Y', 'M', 'D', 'H', 'M', 'S'][i] - - self._cache = s - - return self._cache - -class timeDurationType(durationType): - _validURIs = (NS.XSD, NS.XSD2, NS.ENC) - -class dateTimeType(anyType): - _validURIs = (NS.XSD3,) - - def _checkValueSpace(self, data): - try: - if data == None: - data = time.time() - - if (type(data) in (IntType, LongType)): - data = list(time.gmtime(data)[:6]) - elif (type(data) == FloatType): - f = data - int(data) - data = list(time.gmtime(int(data))[:6]) - data[5] += f - elif type(data) in (ListType, TupleType): - if len(data) < 6: - raise Exception, "not enough values" - if len(data) > 9: - raise Exception, "too many values" - - data = list(data[:6]) - - cleanDate(data) - else: - raise Exception, "invalid type" - except Exception, e: - raise ValueError, "invalid %s value - %s" % (self._type, e) - - return tuple(data) - - def _marshalData(self): - if self._cache == None: - d = self._data - s = "%04d-%02d-%02dT%02d:%02d:%02d" % ((abs(d[0]),) + d[1:]) - if d[0] < 0: - s = '-' + s - f = d[5] - int(d[5]) - if f != 0: - s += ("%g" % f)[1:] - s += 'Z' - - self._cache = s - - return self._cache - -class recurringInstantType(anyType): - _validURIs = (NS.XSD,) - - def _checkValueSpace(self, data): - try: - if data == None: - data = list(time.gmtime(time.time())[:6]) - if (type(data) in (IntType, LongType)): - data = list(time.gmtime(data)[:6]) - elif (type(data) == FloatType): - f = data - int(data) - data = list(time.gmtime(int(data))[:6]) - data[5] += f - elif type(data) in (ListType, TupleType): - if len(data) < 1: - raise Exception, "not enough values" - if len(data) > 9: - raise Exception, "too many values" - - data = list(data[:6]) - - if len(data) < 6: - data += [0] * (6 - len(data)) - - f = len(data) - - for i in range(f): - if data[i] == None: - if f < i: - raise Exception, \ - "only leftmost elements can be none" - else: - f = i - break - - cleanDate(data, f) - else: - raise Exception, "invalid type" - except Exception, e: - raise ValueError, "invalid %s value - %s" % (self._type, e) - - return tuple(data) - - def _marshalData(self): - if self._cache == None: - d = self._data - e = list(d) - neg = '' - - if e[0] < 0: - neg = '-' - e[0] = abs(e[0]) - - if not e[0]: - e[0] = '--' - elif e[0] < 100: - e[0] = '-' + "%02d" % e[0] - else: - e[0] = "%04d" % e[0] - - for i in range(1, len(e)): - if e[i] == None or (i < 3 and e[i] == 0): - e[i] = '-' - else: - if e[i] < 0: - neg = '-' - e[i] = abs(e[i]) - - e[i] = "%02d" % e[i] - - if d[5]: - f = abs(d[5] - int(d[5])) - - if f: - e[5] += ("%g" % f)[1:] - - s = "%s%s-%s-%sT%s:%s:%sZ" % ((neg,) + tuple(e)) - - self._cache = s - - return self._cache - -class timeInstantType(dateTimeType): - _validURIs = (NS.XSD, NS.XSD2, NS.ENC) - -class timePeriodType(dateTimeType): - _validURIs = (NS.XSD2, NS.ENC) - -class timeType(anyType): - def _checkValueSpace(self, data): - try: - if data == None: - data = time.gmtime(time.time())[3:6] - elif (type(data) == FloatType): - f = data - int(data) - data = list(time.gmtime(int(data))[3:6]) - data[2] += f - elif type(data) in (IntType, LongType): - data = time.gmtime(data)[3:6] - elif type(data) in (ListType, TupleType): - if len(data) == 9: - data = data[3:6] - elif len(data) > 3: - raise Exception, "too many values" - - data = [None, None, None] + list(data) - - if len(data) < 6: - data += [0] * (6 - len(data)) - - cleanDate(data, 3) - - data = data[3:] - else: - raise Exception, "invalid type" - except Exception, e: - raise ValueError, "invalid %s value - %s" % (self._type, e) - - return tuple(data) - - def _marshalData(self): - if self._cache == None: - d = self._data - s = '' - - s = time.strftime("%H:%M:%S", (0, 0, 0) + d + (0, 0, -1)) - f = d[2] - int(d[2]) - if f != 0: - s += ("%g" % f)[1:] - s += 'Z' - - self._cache = s - - return self._cache - -class dateType(anyType): - def _checkValueSpace(self, data): - try: - if data == None: - data = time.gmtime(time.time())[0:3] - elif type(data) in (IntType, LongType, FloatType): - data = time.gmtime(data)[0:3] - elif type(data) in (ListType, TupleType): - if len(data) == 9: - data = data[0:3] - elif len(data) > 3: - raise Exception, "too many values" - - data = list(data) - - if len(data) < 3: - data += [1, 1, 1][len(data):] - - data += [0, 0, 0] - - cleanDate(data) - - data = data[:3] - else: - raise Exception, "invalid type" - except Exception, e: - raise ValueError, "invalid %s value - %s" % (self._type, e) - - return tuple(data) - - def _marshalData(self): - if self._cache == None: - d = self._data - s = "%04d-%02d-%02dZ" % ((abs(d[0]),) + d[1:]) - if d[0] < 0: - s = '-' + s - - self._cache = s - - return self._cache - -class gYearMonthType(anyType): - _validURIs = (NS.XSD3,) - - def _checkValueSpace(self, data): - try: - if data == None: - data = time.gmtime(time.time())[0:2] - elif type(data) in (IntType, LongType, FloatType): - data = time.gmtime(data)[0:2] - elif type(data) in (ListType, TupleType): - if len(data) == 9: - data = data[0:2] - elif len(data) > 2: - raise Exception, "too many values" - - data = list(data) - - if len(data) < 2: - data += [1, 1][len(data):] - - data += [1, 0, 0, 0] - - cleanDate(data) - - data = data[:2] - else: - raise Exception, "invalid type" - except Exception, e: - raise ValueError, "invalid %s value - %s" % (self._type, e) - - return tuple(data) - - def _marshalData(self): - if self._cache == None: - d = self._data - s = "%04d-%02dZ" % ((abs(d[0]),) + d[1:]) - if d[0] < 0: - s = '-' + s - - self._cache = s - - return self._cache - -class gYearType(anyType): - _validURIs = (NS.XSD3,) - - def _checkValueSpace(self, data): - try: - if data == None: - data = time.gmtime(time.time())[0:1] - elif type(data) in (IntType, LongType, FloatType): - data = [data] - - if type(data) in (ListType, TupleType): - if len(data) == 9: - data = data[0:1] - elif len(data) < 1: - raise Exception, "too few values" - elif len(data) > 1: - raise Exception, "too many values" - - if type(data[0]) == FloatType: - try: s = int(data[0]) - except: s = long(data[0]) - - if s != data[0]: - raise Exception, "not integral" - - data = [s] - elif type(data[0]) not in (IntType, LongType): - raise Exception, "bad type" - else: - raise Exception, "invalid type" - except Exception, e: - raise ValueError, "invalid %s value - %s" % (self._type, e) - - return data[0] - - def _marshalData(self): - if self._cache == None: - d = self._data - s = "%04dZ" % abs(d) - if d < 0: - s = '-' + s - - self._cache = s - - return self._cache - -class centuryType(anyType): - _validURIs = (NS.XSD2, NS.ENC) - - def _checkValueSpace(self, data): - try: - if data == None: - data = time.gmtime(time.time())[0:1] / 100 - elif type(data) in (IntType, LongType, FloatType): - data = [data] - - if type(data) in (ListType, TupleType): - if len(data) == 9: - data = data[0:1] / 100 - elif len(data) < 1: - raise Exception, "too few values" - elif len(data) > 1: - raise Exception, "too many values" - - if type(data[0]) == FloatType: - try: s = int(data[0]) - except: s = long(data[0]) - - if s != data[0]: - raise Exception, "not integral" - - data = [s] - elif type(data[0]) not in (IntType, LongType): - raise Exception, "bad type" - else: - raise Exception, "invalid type" - except Exception, e: - raise ValueError, "invalid %s value - %s" % (self._type, e) - - return data[0] - - def _marshalData(self): - if self._cache == None: - d = self._data - s = "%02dZ" % abs(d) - if d < 0: - s = '-' + s - - self._cache = s - - return self._cache - -class yearType(gYearType): - _validURIs = (NS.XSD2, NS.ENC) - -class gMonthDayType(anyType): - _validURIs = (NS.XSD3,) - - def _checkValueSpace(self, data): - try: - if data == None: - data = time.gmtime(time.time())[1:3] - elif type(data) in (IntType, LongType, FloatType): - data = time.gmtime(data)[1:3] - elif type(data) in (ListType, TupleType): - if len(data) == 9: - data = data[0:2] - elif len(data) > 2: - raise Exception, "too many values" - - data = list(data) - - if len(data) < 2: - data += [1, 1][len(data):] - - data = [0] + data + [0, 0, 0] - - cleanDate(data, 1) - - data = data[1:3] - else: - raise Exception, "invalid type" - except Exception, e: - raise ValueError, "invalid %s value - %s" % (self._type, e) - - return tuple(data) - - def _marshalData(self): - if self._cache == None: - self._cache = "--%02d-%02dZ" % self._data - - return self._cache - -class recurringDateType(gMonthDayType): - _validURIs = (NS.XSD2, NS.ENC) - -class gMonthType(anyType): - _validURIs = (NS.XSD3,) - - def _checkValueSpace(self, data): - try: - if data == None: - data = time.gmtime(time.time())[1:2] - elif type(data) in (IntType, LongType, FloatType): - data = [data] - - if type(data) in (ListType, TupleType): - if len(data) == 9: - data = data[1:2] - elif len(data) < 1: - raise Exception, "too few values" - elif len(data) > 1: - raise Exception, "too many values" - - if type(data[0]) == FloatType: - try: s = int(data[0]) - except: s = long(data[0]) - - if s != data[0]: - raise Exception, "not integral" - - data = [s] - elif type(data[0]) not in (IntType, LongType): - raise Exception, "bad type" - - if data[0] < 1 or data[0] > 12: - raise Exception, "bad value" - else: - raise Exception, "invalid type" - except Exception, e: - raise ValueError, "invalid %s value - %s" % (self._type, e) - - return data[0] - - def _marshalData(self): - if self._cache == None: - self._cache = "--%02d--Z" % self._data - - return self._cache - -class monthType(gMonthType): - _validURIs = (NS.XSD2, NS.ENC) - -class gDayType(anyType): - _validURIs = (NS.XSD3,) - - def _checkValueSpace(self, data): - try: - if data == None: - data = time.gmtime(time.time())[2:3] - elif type(data) in (IntType, LongType, FloatType): - data = [data] - - if type(data) in (ListType, TupleType): - if len(data) == 9: - data = data[2:3] - elif len(data) < 1: - raise Exception, "too few values" - elif len(data) > 1: - raise Exception, "too many values" - - if type(data[0]) == FloatType: - try: s = int(data[0]) - except: s = long(data[0]) - - if s != data[0]: - raise Exception, "not integral" - - data = [s] - elif type(data[0]) not in (IntType, LongType): - raise Exception, "bad type" - - if data[0] < 1 or data[0] > 31: - raise Exception, "bad value" - else: - raise Exception, "invalid type" - except Exception, e: - raise ValueError, "invalid %s value - %s" % (self._type, e) - - return data[0] - - def _marshalData(self): - if self._cache == None: - self._cache = "---%02dZ" % self._data - - return self._cache - -class recurringDayType(gDayType): - _validURIs = (NS.XSD2, NS.ENC) - -class hexBinaryType(anyType): - _validURIs = (NS.XSD3,) - - def _checkValueSpace(self, data): - if data == None: - raise ValueError, "must supply initial %s value" % self._type - - if type(data) not in (StringType, UnicodeType): - raise AttributeError, "invalid %s type" % self._type - - return data - - def _marshalData(self): - if self._cache == None: - self._cache = encodeHexString(self._data) - - return self._cache - -class base64BinaryType(anyType): - _validURIs = (NS.XSD3,) - - def _checkValueSpace(self, data): - if data == None: - raise ValueError, "must supply initial %s value" % self._type - - if type(data) not in (StringType, UnicodeType): - raise AttributeError, "invalid %s type" % self._type - - return data - - def _marshalData(self): - if self._cache == None: - self._cache = base64.encodestring(self._data) - - return self._cache - -class base64Type(base64BinaryType): - _validURIs = (NS.ENC,) - -class binaryType(anyType): - _validURIs = (NS.XSD, NS.ENC) - - def __init__(self, data, name = None, typed = 1, encoding = 'base64', - attrs = None): - - anyType.__init__(self, data, name, typed, attrs) - - self._setAttr('encoding', encoding) - - def _marshalData(self): - if self._cache == None: - if self._getAttr((None, 'encoding')) == 'base64': - self._cache = base64.encodestring(self._data) - else: - self._cache = encodeHexString(self._data) - - return self._cache - - def _checkValueSpace(self, data): - if data == None: - raise ValueError, "must supply initial %s value" % self._type - - if type(data) not in (StringType, UnicodeType): - raise AttributeError, "invalid %s type" % self._type - - return data - - def _setAttr(self, attr, value): - attr = self._fixAttr(attr) - - if attr[1] == 'encoding': - if attr[0] != None or value not in ('base64', 'hex'): - raise AttributeError, "invalid encoding" - - self._cache = None - - anyType._setAttr(self, attr, value) - - -class anyURIType(anyType): - _validURIs = (NS.XSD3,) - - def _checkValueSpace(self, data): - if data == None: - raise ValueError, "must supply initial %s value" % self._type - - if type(data) not in (StringType, UnicodeType): - raise AttributeError, "invalid %s type" % self._type - - return data - - def _marshalData(self): - if self._cache == None: - self._cache = urllib.quote(self._data) - - return self._cache - -class uriType(anyURIType): - _validURIs = (NS.XSD,) - -class uriReferenceType(anyURIType): - _validURIs = (NS.XSD2,) - -class NOTATIONType(anyType): - def __init__(self, data, name = None, typed = 1, attrs = None): - - if self.__class__ == NOTATIONType: - raise Error, "a NOTATION can't be instantiated directly" - - anyType.__init__(self, data, name, typed, attrs) - -class ENTITIESType(anyType): - def _checkValueSpace(self, data): - if data == None: - raise ValueError, "must supply initial %s value" % self._type - - if type(data) in (StringType, UnicodeType): - return (data,) - - if type(data) not in (ListType, TupleType) or \ - filter (lambda x: type(x) not in (StringType, UnicodeType), data): - raise AttributeError, "invalid %s type" % self._type - - return data - - def _marshalData(self): - return ' '.join(self._data) - -class IDREFSType(ENTITIESType): pass -class NMTOKENSType(ENTITIESType): pass - -class integerType(anyType): - def _checkValueSpace(self, data): - if data == None: - raise ValueError, "must supply initial %s value" % self._type - - if type(data) not in (IntType, LongType): - raise ValueError, "invalid %s value" % self._type - - return data - -class nonPositiveIntegerType(anyType): - _validURIs = (NS.XSD2, NS.XSD3, NS.ENC) - - def _checkValueSpace(self, data): - if data == None: - raise ValueError, "must supply initial %s value" % self._type - - if type(data) not in (IntType, LongType) or data > 0: - raise ValueError, "invalid %s value" % self._type - - return data - -class non_Positive_IntegerType(nonPositiveIntegerType): - _validURIs = (NS.XSD,) - - def _typeName(self): - return 'non-positive-integer' - -class negativeIntegerType(anyType): - _validURIs = (NS.XSD2, NS.XSD3, NS.ENC) - - def _checkValueSpace(self, data): - if data == None: - raise ValueError, "must supply initial %s value" % self._type - - if type(data) not in (IntType, LongType) or data >= 0: - raise ValueError, "invalid %s value" % self._type - - return data - -class negative_IntegerType(negativeIntegerType): - _validURIs = (NS.XSD,) - - def _typeName(self): - return 'negative-integer' - -class longType(anyType): - _validURIs = (NS.XSD2, NS.XSD3, NS.ENC) - - def _checkValueSpace(self, data): - if data == None: - raise ValueError, "must supply initial %s value" % self._type - - if type(data) not in (IntType, LongType) or \ - data < -9223372036854775808L or \ - data > 9223372036854775807L: - raise ValueError, "invalid %s value" % self._type - - return data - -class intType(anyType): - _validURIs = (NS.XSD2, NS.XSD3, NS.ENC) - - def _checkValueSpace(self, data): - if data == None: - raise ValueError, "must supply initial %s value" % self._type - - if type(data) not in (IntType, LongType) or \ - data < -2147483648L or \ - data > 2147483647: - raise ValueError, "invalid %s value" % self._type - - return data - -class shortType(anyType): - _validURIs = (NS.XSD2, NS.XSD3, NS.ENC) - - def _checkValueSpace(self, data): - if data == None: - raise ValueError, "must supply initial %s value" % self._type - - if type(data) not in (IntType, LongType) or \ - data < -32768 or \ - data > 32767: - raise ValueError, "invalid %s value" % self._type - - return data - -class byteType(anyType): - _validURIs = (NS.XSD2, NS.XSD3, NS.ENC) - - def _checkValueSpace(self, data): - if data == None: - raise ValueError, "must supply initial %s value" % self._type - - if type(data) not in (IntType, LongType) or \ - data < -128 or \ - data > 127: - raise ValueError, "invalid %s value" % self._type - - return data - -class nonNegativeIntegerType(anyType): - _validURIs = (NS.XSD2, NS.XSD3, NS.ENC) - - def _checkValueSpace(self, data): - if data == None: - raise ValueError, "must supply initial %s value" % self._type - - if type(data) not in (IntType, LongType) or data < 0: - raise ValueError, "invalid %s value" % self._type - - return data - -class non_Negative_IntegerType(nonNegativeIntegerType): - _validURIs = (NS.XSD,) - - def _typeName(self): - return 'non-negative-integer' - -class unsignedLongType(anyType): - _validURIs = (NS.XSD2, NS.XSD3, NS.ENC) - - def _checkValueSpace(self, data): - if data == None: - raise ValueError, "must supply initial %s value" % self._type - - if type(data) not in (IntType, LongType) or \ - data < 0 or \ - data > 18446744073709551615L: - raise ValueError, "invalid %s value" % self._type - - return data - -class unsignedIntType(anyType): - _validURIs = (NS.XSD2, NS.XSD3, NS.ENC) - - def _checkValueSpace(self, data): - if data == None: - raise ValueError, "must supply initial %s value" % self._type - - if type(data) not in (IntType, LongType) or \ - data < 0 or \ - data > 4294967295L: - raise ValueError, "invalid %s value" % self._type - - return data - -class unsignedShortType(anyType): - _validURIs = (NS.XSD2, NS.XSD3, NS.ENC) - - def _checkValueSpace(self, data): - if data == None: - raise ValueError, "must supply initial %s value" % self._type - - if type(data) not in (IntType, LongType) or \ - data < 0 or \ - data > 65535: - raise ValueError, "invalid %s value" % self._type - - return data - -class unsignedByteType(anyType): - _validURIs = (NS.XSD2, NS.XSD3, NS.ENC) - - def _checkValueSpace(self, data): - if data == None: - raise ValueError, "must supply initial %s value" % self._type - - if type(data) not in (IntType, LongType) or \ - data < 0 or \ - data > 255: - raise ValueError, "invalid %s value" % self._type - - return data - -class positiveIntegerType(anyType): - _validURIs = (NS.XSD2, NS.XSD3, NS.ENC) - - def _checkValueSpace(self, data): - if data == None: - raise ValueError, "must supply initial %s value" % self._type - - if type(data) not in (IntType, LongType) or data <= 0: - raise ValueError, "invalid %s value" % self._type - - return data - -class positive_IntegerType(positiveIntegerType): - _validURIs = (NS.XSD,) - - def _typeName(self): - return 'positive-integer' - -# Now compound types - -class compoundType(anyType): - def __init__(self, data = None, name = None, typed = 1, attrs = None): - if self.__class__ == compoundType: - raise Error, "a compound can't be instantiated directly" - - anyType.__init__(self, data, name, typed, attrs) - self._aslist = [] - self._asdict = {} - self._keyord = [] - - if type(data) == DictType: - self.__dict__.update(data) - - def __getitem__(self, item): - if type(item) == IntType: - return self._aslist[item] - return getattr(self, item) - - def __len__(self): - return len(self._aslist) - - def __nonzero__(self): - return 1 - - def _keys(self): - return filter(lambda x: x[0] != '_', self.__dict__.keys()) - - def _addItem(self, name, value, attrs = None): - d = self._asdict - - if d.has_key(name): - if type(d[name]) != ListType: - d[name] = [d[name]] - d[name].append(value) - else: - d[name] = value - - self._keyord.append(name) - self._aslist.append(value) - self.__dict__[name] = d[name] - - def _placeItem(self, name, value, pos, subpos = 0, attrs = None): - d = self._asdict - - if subpos == 0 and type(d[name]) != ListType: - d[name] = value - else: - d[name][subpos] = value - - self._keyord[pos] = name - self._aslist[pos] = value - self.__dict__[name] = d[name] - - def _getItemAsList(self, name, default = []): - try: - d = self.__dict__[name] - except: - return default - - if type(d) == ListType: - return d - return [d] - -class structType(compoundType): - pass - -class headerType(structType): - _validURIs = (NS.ENV,) - - def __init__(self, data = None, typed = 1, attrs = None): - structType.__init__(self, data, "Header", typed, attrs) - -class bodyType(structType): - _validURIs = (NS.ENV,) - - def __init__(self, data = None, typed = 1, attrs = None): - structType.__init__(self, data, "Body", typed, attrs) - -class arrayType(UserList.UserList, compoundType): - def __init__(self, data = None, name = None, attrs = None, - offset = 0, rank = None, asize = 0, elemsname = None): - - if data: - if type(data) not in (ListType, TupleType): - raise Error, "Data must be a sequence" - - UserList.UserList.__init__(self, data) - compoundType.__init__(self, data, name, 0, attrs) - - self._elemsname = elemsname or "item" - - if data == None: - self._rank = rank - - # According to 5.4.2.2 in the SOAP spec, each element in a - # sparse array must have a position. _posstate keeps track of - # whether we've seen a position or not. It's possible values - # are: - # -1 No elements have been added, so the state is indeterminate - # 0 An element without a position has been added, so no - # elements can have positions - # 1 An element with a position has been added, so all elements - # must have positions - - self._posstate = -1 - - self._full = 0 - - if asize in ('', None): - asize = '0' - - self._dims = map (lambda x: int(x), str(asize).split(',')) - self._dims.reverse() # It's easier to work with this way - self._poss = [0] * len(self._dims) # This will end up - # reversed too - - for i in range(len(self._dims)): - if self._dims[i] < 0 or \ - self._dims[i] == 0 and len(self._dims) > 1: - raise TypeError, "invalid Array dimensions" - - if offset > 0: - self._poss[i] = offset % self._dims[i] - offset = int(offset / self._dims[i]) - - # Don't break out of the loop if offset is 0 so we test all the - # dimensions for > 0. - if offset: - raise AttributeError, "invalid Array offset" - - a = [None] * self._dims[0] - - for i in range(1, len(self._dims)): - b = [] - - for j in range(self._dims[i]): - b.append(copy.deepcopy(a)) - - a = b - - self.data = a - - def _addItem(self, name, value, attrs): - if self._full: - raise ValueError, "Array is full" - - pos = attrs.get((NS.ENC, 'position')) - - if pos != None: - if self._posstate == 0: - raise AttributeError, \ - "all elements in a sparse Array must have a " \ - "position attribute" - - self._posstate = 1 - - try: - if pos[0] == '[' and pos[-1] == ']': - pos = map (lambda x: int(x), pos[1:-1].split(',')) - pos.reverse() - - if len(pos) == 1: - pos = pos[0] - - curpos = [0] * len(self._dims) - - for i in range(len(self._dims)): - curpos[i] = pos % self._dims[i] - pos = int(pos / self._dims[i]) - - if pos == 0: - break - - if pos: - raise Exception - elif len(pos) != len(self._dims): - raise Exception - else: - for i in range(len(self._dims)): - if pos[i] >= self._dims[i]: - raise Exception - - curpos = pos - else: - raise Exception - except: - raise AttributeError, \ - "invalid Array element position %s" % str(pos) - else: - if self._posstate == 1: - raise AttributeError, \ - "only elements in a sparse Array may have a " \ - "position attribute" - - self._posstate = 0 - - curpos = self._poss - - a = self.data - - for i in range(len(self._dims) - 1, 0, -1): - a = a[curpos[i]] - - if curpos[0] >= len(a): - a += [None] * (len(a) - curpos[0] + 1) - - a[curpos[0]] = value - - if pos == None: - self._poss[0] += 1 - - for i in range(len(self._dims) - 1): - if self._poss[i] < self._dims[i]: - break - - self._poss[i] = 0 - self._poss[i + 1] += 1 - - if self._dims[-1] and self._poss[-1] >= self._dims[-1]: - self._full = 1 - - def _placeItem(self, name, value, pos, subpos, attrs = None): - curpos = [0] * len(self._dims) - - for i in range(len(self._dims)): - if self._dims[i] == 0: - curpos[0] = pos - break - - curpos[i] = pos % self._dims[i] - pos = int(pos / self._dims[i]) - - if pos == 0: - break - - if self._dims[i] != 0 and pos: - raise Error, "array index out of range" - - a = self.data - - for i in range(len(self._dims) - 1, 0, -1): - a = a[curpos[i]] - - if curpos[0] >= len(a): - a += [None] * (len(a) - curpos[0] + 1) - - a[curpos[0]] = value - -class typedArrayType(arrayType): - def __init__(self, data = None, name = None, typed = None, attrs = None, - offset = 0, rank = None, asize = 0, elemsname = None): - - arrayType.__init__(self, data, name, attrs, offset, rank, asize, - elemsname) - - self._type = typed - -class faultType(structType, Error): - def __init__(self, faultcode = "", faultstring = "", detail = None): - self.faultcode = faultcode - self.faultstring = faultstring - if detail != None: - self.detail = detail - - structType.__init__(self, None, 0) - - def _setDetail(self, detail = None): - if detail != None: - self.detail = detail - else: - try: del self.detail - except AttributeError: pass - - def __repr__(self): - return "" % (self.faultcode, self.faultstring) - - __str__ = __repr__ - -################################################################################ -class RefHolder: - def __init__(self, name, frame): - self.name = name - self.parent = frame - self.pos = len(frame) - self.subpos = frame.namecounts.get(name, 0) - - def __repr__(self): - return "<%s %s at %d>" % (self.__class__, self.name, id(self)) - -################################################################################ -# Utility infielders -################################################################################ -def collapseWhiteSpace(s): - return re.sub('\s+', ' ', s).strip() - -def decodeHexString(data): - conv = {'0': 0x0, '1': 0x1, '2': 0x2, '3': 0x3, '4': 0x4, - '5': 0x5, '6': 0x6, '7': 0x7, '8': 0x8, '9': 0x9, 'a': 0xa, - 'b': 0xb, 'c': 0xc, 'd': 0xd, 'e': 0xe, 'f': 0xf, 'A': 0xa, - 'B': 0xb, 'C': 0xc, 'D': 0xd, 'E': 0xe, 'F': 0xf,} - ws = string.whitespace - - bin = '' - - i = 0 - - while i < len(data): - if data[i] not in ws: - break - i += 1 - - low = 0 - - while i < len(data): - c = data[i] - - if c in string.whitespace: - break - - try: - c = conv[c] - except KeyError: - raise ValueError, \ - "invalid hex string character `%s'" % c - - if low: - bin += chr(high * 16 + c) - low = 0 - else: - high = c - low = 1 - - i += 1 - - if low: - raise ValueError, "invalid hex string length" - - while i < len(data): - if data[i] not in string.whitespace: - raise ValueError, \ - "invalid hex string character `%s'" % c - - i += 1 - - return bin - -def encodeHexString(data): - h = '' - - for i in data: - h += "%02X" % ord(i) - - return h - -def leapMonth(year, month): - return month == 2 and \ - year % 4 == 0 and \ - (year % 100 != 0 or year % 400 == 0) - -def cleanDate(d, first = 0): - ranges = (None, (1, 12), (1, 31), (0, 23), (0, 59), (0, 61)) - months = (0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31) - names = ('year', 'month', 'day', 'hours', 'minutes', 'seconds') - - if len(d) != 6: - raise ValueError, "date must have 6 elements" - - for i in range(first, 6): - s = d[i] - - if type(s) == FloatType: - if i < 5: - try: - s = int(s) - except OverflowError: - if i > 0: - raise - s = long(s) - - if s != d[i]: - raise ValueError, "%s must be integral" % names[i] - - d[i] = s - elif type(s) == LongType: - try: s = int(s) - except: pass - elif type(s) != IntType: - raise TypeError, "%s isn't a valid type" % names[i] - - if i == first and s < 0: - continue - - if ranges[i] != None and \ - (s < ranges[i][0] or ranges[i][1] < s): - raise ValueError, "%s out of range" % names[i] - - if first < 6 and d[5] >= 61: - raise ValueError, "seconds out of range" - - if first < 2: - leap = first < 1 and leapMonth(d[0], d[1]) - - if d[2] > months[d[1]] + leap: - raise ValueError, "day out of range" - -class UnderflowError(exceptions.ArithmeticError): - pass - -def debugHeader(title): - s = '*** ' + title + ' ' - print s + ('*' * (72 - len(s))) - -def debugFooter(title): - print '*' * 72 - sys.stdout.flush() - -################################################################################ -# SOAP Parser -################################################################################ -class SOAPParser(xml.sax.handler.ContentHandler): - class Frame: - def __init__(self, name, kind = None, attrs = {}, rules = {}): - self.name = name - self.kind = kind - self.attrs = attrs - self.rules = rules - - self.contents = [] - self.names = [] - self.namecounts = {} - self.subattrs = [] - - def append(self, name, data, attrs): - self.names.append(name) - self.contents.append(data) - self.subattrs.append(attrs) - - if self.namecounts.has_key(name): - self.namecounts[name] += 1 - else: - self.namecounts[name] = 1 - - def _placeItem(self, name, value, pos, subpos = 0, attrs = None): - self.contents[pos] = value - - if attrs: - self.attrs.update(attrs) - - def __len__(self): - return len(self.contents) - - def __repr__(self): - return "<%s %s at %d>" % (self.__class__, self.name, id(self)) - - def __init__(self, rules = None): - xml.sax.handler.ContentHandler.__init__(self) - self.body = None - self.header = None - self.attrs = {} - self._data = None - self._next = "E" # Keeping state for message validity - self._stack = [self.Frame('SOAP')] - - # Make two dictionaries to store the prefix <-> URI mappings, and - # initialize them with the default - self._prem = {NS.XML_T: NS.XML} - self._prem_r = {NS.XML: NS.XML_T} - self._ids = {} - self._refs = {} - self._rules = rules - - def startElementNS(self, name, qname, attrs): - # Workaround two sax bugs - if name[0] == None and name[1][0] == ' ': - name = (None, name[1][1:]) - else: - name = tuple(name) - - # First some checking of the layout of the message - - if self._next == "E": - if name[1] != 'Envelope': - raise Error, "expected `SOAP-ENV:Envelope', got `%s:%s'" % \ - (self._prem_r[name[0]], name[1]) - if name[0] != NS.ENV: - raise faultType, ("%s:VersionMismatch" % NS.ENV_T, - "Don't understand version `%s' Envelope" % name[0]) - else: - self._next = "HorB" - elif self._next == "HorB": - if name[0] == NS.ENV and name[1] in ("Header", "Body"): - self._next = None - else: - raise Error, \ - "expected `SOAP-ENV:Header' or `SOAP-ENV:Body', " \ - "got `%s'" % self._prem_r[name[0]] + ':' + name[1] - elif self._next == "B": - if name == (NS.ENV, "Body"): - self._next = None - else: - raise Error, "expected `SOAP-ENV:Body', got `%s'" % \ - self._prem_r[name[0]] + ':' + name[1] - elif self._next == "": - raise Error, "expected nothing, got `%s'" % \ - self._prem_r[name[0]] + ':' + name[1] - - if len(self._stack) == 2: - rules = self._rules - else: - try: - rules = self._stack[-1].rules[name[1]] - except: - rules = None - - if type(rules) not in (NoneType, DictType): - kind = rules - else: - kind = attrs.get((NS.ENC, 'arrayType')) - - if kind != None: - del attrs._attrs[(NS.ENC, 'arrayType')] - - i = kind.find(':') - if i >= 0: - kind = (self._prem[kind[:i]], kind[i + 1:]) - else: - kind = None - - self.pushFrame(self.Frame(name[1], kind, attrs._attrs, rules)) - - self._data = '' # Start accumulating - - def pushFrame(self, frame): - self._stack.append(frame) - - def popFrame(self): - return self._stack.pop() - - def endElementNS(self, name, qname): - # Workaround two sax bugs - if name[0] == None and name[1][0] == ' ': - ns, name = None, name[1][1:] - else: - ns, name = tuple(name) - - if self._next == "E": - raise Error, "didn't get SOAP-ENV:Envelope" - if self._next in ("HorB", "B"): - raise Error, "didn't get SOAP-ENV:Body" - - cur = self.popFrame() - attrs = cur.attrs - - idval = None - - if attrs.has_key((None, 'id')): - idval = attrs[(None, 'id')] - - if self._ids.has_key(idval): - raise Error, "duplicate id `%s'" % idval - - del attrs[(None, 'id')] - - root = 1 - - if len(self._stack) == 3: - if attrs.has_key((NS.ENC, 'root')): - root = int(attrs[(NS.ENC, 'root')]) - - # Do some preliminary checks. First, if root="0" is present, - # the element must have an id. Next, if root="n" is present, - # n something other than 0 or 1, raise an exception. - - if root == 0: - if idval == None: - raise Error, "non-root element must have an id" - elif root != 1: - raise Error, "SOAP-ENC:root must be `0' or `1'" - - del attrs[(NS.ENC, 'root')] - - while 1: - href = attrs.get((None, 'href')) - if href: - if href[0] != '#': - raise Error, "only do local hrefs right now" - if self._data != None and self._data.strip() != '': - raise Error, "hrefs can't have data" - - href = href[1:] - - if self._ids.has_key(href): - data = self._ids[href] - else: - data = RefHolder(name, self._stack[-1]) - - if self._refs.has_key(href): - self._refs[href].append(data) - else: - self._refs[href] = [data] - - del attrs[(None, 'href')] - - break - - kind = None - - if attrs: - for i in NS.XSI_L: - if attrs.has_key((i, 'type')): - kind = attrs[(i, 'type')] - del attrs[(i, 'type')] - - if kind != None: - i = kind.find(':') - if i >= 0: - kind = (self._prem[kind[:i]], kind[i + 1:]) - else: -# XXX What to do here? (None, kind) is just going to fail in convertType - kind = (None, kind) - - null = 0 - - if attrs: - for i in (NS.XSI, NS.XSI2): - if attrs.has_key((i, 'null')): - null = attrs[(i, 'null')] - del attrs[(i, 'null')] - - if attrs.has_key((NS.XSI3, 'nil')): - null = attrs[(NS.XSI3, 'nil')] - del attrs[(NS.XSI3, 'nil')] - - #MAP 4/12/2002 - must also support "true" - #null = int(null) - null = (str(null).lower() in ['true', '1']) - - if null: - if len(cur) or \ - (self._data != None and self._data.strip() != ''): - raise Error, "nils can't have data" - - data = None - - break - - if len(self._stack) == 2: - if (ns, name) == (NS.ENV, "Header"): - self.header = data = headerType(attrs = attrs) - self._next = "B" - break - elif (ns, name) == (NS.ENV, "Body"): - self.body = data = bodyType(attrs = attrs) - self._next = "" - break - elif len(self._stack) == 3 and self._next == None: - if (ns, name) == (NS.ENV, "Fault"): - data = faultType() - self._next = "" - break - - if cur.rules != None: - rule = cur.rules - - if type(rule) in (StringType, UnicodeType): -# XXX Need a namespace here - rule = (None, rule) - elif type(rule) == ListType: - rule = tuple(rule) - -# XXX What if rule != kind? - if callable(rule): - data = rule(self._data) - elif type(rule) == DictType: - data = structType(name = (ns, name), attrs = attrs) - else: - data = self.convertType(self._data, rule, attrs) - - break - - if (kind == None and cur.kind != None) or \ - (kind == (NS.ENC, 'Array')): - kind = cur.kind - - if kind == None: - kind = 'ur-type[%d]' % len(cur) - else: - kind = kind[1] - - if len(cur.namecounts) == 1: - elemsname = cur.names[0] - else: - elemsname = None - - data = self.startArray((ns, name), kind, attrs, elemsname) - - break - - if len(self._stack) == 3 and kind == None and \ - len(cur) == 0 and \ - (self._data == None or self._data.strip() == ''): - data = structType(name = (ns, name), attrs = attrs) - break - - if len(cur) == 0 and ns != NS.URN: - # Nothing's been added to the current frame so it must be a - # simple type. - - if kind == None: - # If the current item's container is an array, it will - # have a kind. If so, get the bit before the first [, - # which is the type of the array, therefore the type of - # the current item. - - kind = self._stack[-1].kind - - if kind != None: - i = kind[1].find('[') - if i >= 0: - kind = (kind[0], kind[1][:i]) - elif ns != None: - kind = (ns, name) - - if kind != None: - try: - data = self.convertType(self._data, kind, attrs) - except UnknownTypeError: - data = None - else: - data = None - - if data == None: - data = self._data or '' - - if len(attrs) == 0: - try: data = str(data) - except: pass - - break - - data = structType(name = (ns, name), attrs = attrs) - - break - - if isinstance(data, compoundType): - for i in range(len(cur)): - v = cur.contents[i] - data._addItem(cur.names[i], v, cur.subattrs[i]) - - if isinstance(v, RefHolder): - v.parent = data - - if root: - self._stack[-1].append(name, data, attrs) - - if idval != None: - self._ids[idval] = data - - if self._refs.has_key(idval): - for i in self._refs[idval]: - i.parent._placeItem(i.name, data, i.pos, i.subpos, attrs) - - del self._refs[idval] - - self.attrs[id(data)] = attrs - - if isinstance(data, anyType): - data._setAttrs(attrs) - - self._data = None # Stop accumulating - - def endDocument(self): - if len(self._refs) == 1: - raise Error, \ - "unresolved reference " + self._refs.keys()[0] - elif len(self._refs) > 1: - raise Error, \ - "unresolved references " + ', '.join(self._refs.keys()) - - def startPrefixMapping(self, prefix, uri): - self._prem[prefix] = uri - self._prem_r[uri] = prefix - - def endPrefixMapping(self, prefix): - try: - del self._prem_r[self._prem[prefix]] - del self._prem[prefix] - except: - pass - - def characters(self, c): - if self._data != None: - self._data += c - - arrayre = '^(?:(?P[^:]*):)?' \ - '(?P[^[]+)' \ - '(?:\[(?P,*)\])?' \ - '(?:\[(?P\d+(?:,\d+)*)?\])$' - - def startArray(self, name, kind, attrs, elemsname): - if type(self.arrayre) == StringType: - self.arrayre = re.compile (self.arrayre) - - offset = attrs.get((NS.ENC, "offset")) - - if offset != None: - del attrs[(NS.ENC, "offset")] - - try: - if offset[0] == '[' and offset[-1] == ']': - offset = int(offset[1:-1]) - if offset < 0: - raise Exception - else: - raise Exception - except: - raise AttributeError, "invalid Array offset" - else: - offset = 0 - - try: - m = self.arrayre.search(kind) - - if m == None: - raise Exception - - t = m.group('type') - - if t == 'ur-type': - return arrayType(None, name, attrs, offset, m.group('rank'), - m.group('asize'), elemsname) - elif m.group('ns') != None: - return typedArrayType(None, name, - (self._prem[m.group('ns')], t), attrs, offset, - m.group('rank'), m.group('asize'), elemsname) - else: - return typedArrayType(None, name, (None, t), attrs, offset, - m.group('rank'), m.group('asize'), elemsname) - except: - raise AttributeError, "invalid Array type `%s'" % kind - - # Conversion - - class DATETIMECONSTS: - SIGNre = '(?P-?)' - CENTURYre = '(?P\d{2,})' - YEARre = '(?P\d{2})' - MONTHre = '(?P\d{2})' - DAYre = '(?P\d{2})' - HOURre = '(?P\d{2})' - MINUTEre = '(?P\d{2})' - SECONDre = '(?P\d{2}(?:\.\d*)?)' - TIMEZONEre = '(?PZ)|(?P[-+])(?P\d{2}):' \ - '(?P\d{2})' - BOSre = '^\s*' - EOSre = '\s*$' - - __allres = {'sign': SIGNre, 'century': CENTURYre, 'year': YEARre, - 'month': MONTHre, 'day': DAYre, 'hour': HOURre, - 'minute': MINUTEre, 'second': SECONDre, 'timezone': TIMEZONEre, - 'b': BOSre, 'e': EOSre} - - dateTime = '%(b)s%(sign)s%(century)s%(year)s-%(month)s-%(day)sT' \ - '%(hour)s:%(minute)s:%(second)s(%(timezone)s)?%(e)s' % __allres - timeInstant = dateTime - timePeriod = dateTime - time = '%(b)s%(hour)s:%(minute)s:%(second)s(%(timezone)s)?%(e)s' % \ - __allres - date = '%(b)s%(sign)s%(century)s%(year)s-%(month)s-%(day)s' \ - '(%(timezone)s)?%(e)s' % __allres - century = '%(b)s%(sign)s%(century)s(%(timezone)s)?%(e)s' % __allres - gYearMonth = '%(b)s%(sign)s%(century)s%(year)s-%(month)s' \ - '(%(timezone)s)?%(e)s' % __allres - gYear = '%(b)s%(sign)s%(century)s%(year)s(%(timezone)s)?%(e)s' % \ - __allres - year = gYear - gMonthDay = '%(b)s--%(month)s-%(day)s(%(timezone)s)?%(e)s' % __allres - recurringDate = gMonthDay - gDay = '%(b)s---%(day)s(%(timezone)s)?%(e)s' % __allres - recurringDay = gDay - gMonth = '%(b)s--%(month)s--(%(timezone)s)?%(e)s' % __allres - month = gMonth - - recurringInstant = '%(b)s%(sign)s(%(century)s|-)(%(year)s|-)-' \ - '(%(month)s|-)-(%(day)s|-)T' \ - '(%(hour)s|-):(%(minute)s|-):(%(second)s|-)' \ - '(%(timezone)s)?%(e)s' % __allres - - duration = '%(b)s%(sign)sP' \ - '((?P\d+)Y)?' \ - '((?P\d+)M)?' \ - '((?P\d+)D)?' \ - '((?PT)' \ - '((?P\d+)H)?' \ - '((?P\d+)M)?' \ - '((?P\d*(?:\.\d*)?)S)?)?%(e)s' % \ - __allres - - timeDuration = duration - - # The extra 31 on the front is: - # - so the tuple is 1-based - # - so months[month-1] is December's days if month is 1 - - months = (31, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31) - - def convertDateTime(self, value, kind): - def getZoneOffset(d): - zoffs = 0 - - try: - if d['zulu'] == None: - zoffs = 60 * int(d['tzhour']) + int(d['tzminute']) - if d['tzsign'] != '-': - zoffs = -zoffs - except TypeError: - pass - - return zoffs - - def applyZoneOffset(months, zoffs, date, minfield, posday = 1): - if zoffs == 0 and (minfield > 4 or 0 <= date[5] < 60): - return date - - if minfield > 5: date[5] = 0 - if minfield > 4: date[4] = 0 - - if date[5] < 0: - date[4] += int(date[5]) / 60 - date[5] %= 60 - - date[4] += zoffs - - if minfield > 3 or 0 <= date[4] < 60: return date - - date[3] += date[4] / 60 - date[4] %= 60 - - if minfield > 2 or 0 <= date[3] < 24: return date - - date[2] += date[3] / 24 - date[3] %= 24 - - if minfield > 1: - if posday and date[2] <= 0: - date[2] += 31 # zoffs is at most 99:59, so the - # day will never be less than -3 - return date - - while 1: - # The date[1] == 3 (instead of == 2) is because we're - # going back a month, so we need to know if the previous - # month is February, so we test if this month is March. - - leap = minfield == 0 and date[1] == 3 and \ - date[0] % 4 == 0 and \ - (date[0] % 100 != 0 or date[0] % 400 == 0) - - if 0 < date[2] <= months[date[1]] + leap: break - - date[2] += months[date[1] - 1] + leap - - date[1] -= 1 - - if date[1] > 0: break - - date[1] = 12 - - if minfield > 0: break - - date[0] -= 1 - - return date - - try: - exp = getattr(self.DATETIMECONSTS, kind) - except AttributeError: - return None - - if type(exp) == StringType: - exp = re.compile(exp) - setattr (self.DATETIMECONSTS, kind, exp) - - m = exp.search(value) - - try: - if m == None: - raise Exception - - d = m.groupdict() - f = ('century', 'year', 'month', 'day', - 'hour', 'minute', 'second') - fn = len(f) # Index of first non-None value - r = [] - - if kind in ('duration', 'timeDuration'): - if d['sep'] != None and d['hour'] == None and \ - d['minute'] == None and d['second'] == None: - raise Exception - - f = f[1:] - - for i in range(len(f)): - s = d[f[i]] - - if s != None: - if f[i] == 'second': - s = float(s) - else: - try: s = int(s) - except ValueError: s = long(s) - - if i < fn: fn = i - - r.append(s) - - if fn > len(r): # Any non-Nones? - raise Exception - - if d['sign'] == '-': - r[fn] = -r[fn] - - return tuple(r) - - if kind == 'recurringInstant': - for i in range(len(f)): - s = d[f[i]] - - if s == None or s == '-': - if i > fn: - raise Exception - s = None - else: - if i < fn: - fn = i - - if f[i] == 'second': - s = float(s) - else: - try: - s = int(s) - except ValueError: - s = long(s) - - r.append(s) - - s = r.pop(0) - - if fn == 0: - r[0] += s * 100 - else: - fn -= 1 - - if fn < len(r) and d['sign'] == '-': - r[fn] = -r[fn] - - cleanDate(r, fn) - - return tuple(applyZoneOffset(self.DATETIMECONSTS.months, - getZoneOffset(d), r, fn, 0)) - - r = [0, 0, 1, 1, 0, 0, 0] - - for i in range(len(f)): - field = f[i] - - s = d.get(field) - - if s != None: - if field == 'second': - s = float(s) - else: - try: - s = int(s) - except ValueError: - s = long(s) - - if i < fn: - fn = i - - r[i] = s - - if fn > len(r): # Any non-Nones? - raise Exception - - s = r.pop(0) - - if fn == 0: - r[0] += s * 100 - else: - fn -= 1 - - if d.get('sign') == '-': - r[fn] = -r[fn] - - cleanDate(r, fn) - - zoffs = getZoneOffset(d) - - if zoffs: - r = applyZoneOffset(self.DATETIMECONSTS.months, zoffs, r, fn) - - if kind == 'century': - return r[0] / 100 - - s = [] - - for i in range(1, len(f)): - if d.has_key(f[i]): - s.append(r[i - 1]) - - if len(s) == 1: - return s[0] - return tuple(s) - except Exception, e: - raise Error, "invalid %s value `%s' - %s" % (kind, value, e) - - intlimits = \ - { - 'nonPositiveInteger': (0, None, 0), - 'non-positive-integer': (0, None, 0), - 'negativeInteger': (0, None, -1), - 'negative-integer': (0, None, -1), - 'long': (1, -9223372036854775808L, - 9223372036854775807L), - 'int': (0, -2147483648L, 2147483647), - 'short': (0, -32768, 32767), - 'byte': (0, -128, 127), - 'nonNegativeInteger': (0, 0, None), - 'non-negative-integer': (0, 0, None), - 'positiveInteger': (0, 1, None), - 'positive-integer': (0, 1, None), - 'unsignedLong': (1, 0, 18446744073709551615L), - 'unsignedInt': (0, 0, 4294967295L), - 'unsignedShort': (0, 0, 65535), - 'unsignedByte': (0, 0, 255), - } - floatlimits = \ - { - 'float': (7.0064923216240861E-46, -3.4028234663852886E+38, - 3.4028234663852886E+38), - 'double': (2.4703282292062327E-324, -1.7976931348623158E+308, - 1.7976931348623157E+308), - } - zerofloatre = '[1-9]' - - def convertType(self, d, t, attrs): - dnn = d or '' - - if t[0] in NS.EXSD_L: - if t[1] == "integer": - try: - d = int(d) - if len(attrs): - d = long(d) - except: - d = long(d) - return d - if self.intlimits.has_key (t[1]): - l = self.intlimits[t[1]] - try: d = int(d) - except: d = long(d) - - if l[1] != None and d < l[1]: - raise UnderflowError, "%s too small" % d - if l[2] != None and d > l[2]: - raise OverflowError, "%s too large" % d - - if l[0] or len(attrs): - return long(d) - return d - if t[1] == "string": - if len(attrs): - return unicode(dnn) - try: - return str(dnn) - except: - return dnn - if t[1] == "boolean": - d = d.strip().lower() - if d in ('0', 'false'): - return 0 - if d in ('1', 'true'): - return 1 - raise AttributeError, "invalid boolean value" - if self.floatlimits.has_key (t[1]): - l = self.floatlimits[t[1]] - s = d.strip().lower() - try: - d = float(s) - except: - # Some platforms don't implement the float stuff. This - # is close, but NaN won't be > "INF" as required by the - # standard. - - if s in ("nan", "inf"): - return 1e300**2 - if s == "-inf": - return -1e300**2 - - raise - - if str (d) == 'nan': - if s != 'nan': - raise ValueError, "invalid %s" % t[1] - elif str (d) == '-inf': - if s != '-inf': - raise UnderflowError, "%s too small" % t[1] - elif str (d) == 'inf': - if s != 'inf': - raise OverflowError, "%s too large" % t[1] - elif d < 0: - if d < l[1]: - raise UnderflowError, "%s too small" % t[1] - elif d > 0: - if d < l[0] or d > l[2]: - raise OverflowError, "%s too large" % t[1] - elif d == 0: - if type(self.zerofloatre) == StringType: - self.zerofloatre = re.compile(self.zerofloatre) - - if self.zerofloatre.search(s): - raise UnderflowError, "invalid %s" % t[1] - - return d - if t[1] in ("dateTime", "date", "timeInstant", "time"): - return self.convertDateTime(d, t[1]) - if t[1] == "decimal": - return float(d) - if t[1] in ("language", "QName", "NOTATION", "NMTOKEN", "Name", - "NCName", "ID", "IDREF", "ENTITY"): - return collapseWhiteSpace(d) - if t[1] in ("IDREFS", "ENTITIES", "NMTOKENS"): - d = collapseWhiteSpace(d) - return d.split() - if t[0] in NS.XSD_L: - if t[1] in ("base64", "base64Binary"): - return base64.decodestring(d) - if t[1] == "hexBinary": - return decodeHexString(d) - if t[1] == "anyURI": - return urllib.unquote(collapseWhiteSpace(d)) - if t[1] in ("normalizedString", "token"): - return collapseWhiteSpace(d) - if t[0] == NS.ENC: - if t[1] == "base64": - return base64.decodestring(d) - if t[0] == NS.XSD: - if t[1] == "binary": - try: - e = attrs[(None, 'encoding')] - - if e == 'hex': - return decodeHexString(d) - elif e == 'base64': - return base64.decodestring(d) - except: - pass - - raise Error, "unknown or missing binary encoding" - if t[1] == "uri": - return urllib.unquote(collapseWhiteSpace(d)) - if t[1] == "recurringInstant": - return self.convertDateTime(d, t[1]) - if t[0] in (NS.XSD2, NS.ENC): - if t[1] == "uriReference": - return urllib.unquote(collapseWhiteSpace(d)) - if t[1] == "timePeriod": - return self.convertDateTime(d, t[1]) - if t[1] in ("century", "year"): - return self.convertDateTime(d, t[1]) - if t[0] in (NS.XSD, NS.XSD2, NS.ENC): - if t[1] == "timeDuration": - return self.convertDateTime(d, t[1]) - if t[0] == NS.XSD3: - if t[1] == "anyURI": - return urllib.unquote(collapseWhiteSpace(d)) - if t[1] in ("gYearMonth", "gMonthDay"): - return self.convertDateTime(d, t[1]) - if t[1] == "gYear": - return self.convertDateTime(d, t[1]) - if t[1] == "gMonth": - return self.convertDateTime(d, t[1]) - if t[1] == "gDay": - return self.convertDateTime(d, t[1]) - if t[1] == "duration": - return self.convertDateTime(d, t[1]) - if t[0] in (NS.XSD2, NS.XSD3): - if t[1] == "token": - return collapseWhiteSpace(d) - if t[1] == "recurringDate": - return self.convertDateTime(d, t[1]) - if t[1] == "month": - return self.convertDateTime(d, t[1]) - if t[1] == "recurringDay": - return self.convertDateTime(d, t[1]) - if t[0] == NS.XSD2: - if t[1] == "CDATA": - return collapseWhiteSpace(d) - - raise UnknownTypeError, "unknown type `%s'" % (t[0] + ':' + t[1]) - -################################################################################ -# call to SOAPParser that keeps all of the info -################################################################################ -def _parseSOAP(xml_str, rules = None): - try: - from cStringIO import StringIO - except ImportError: - from StringIO import StringIO - - parser = xml.sax.make_parser() - t = SOAPParser(rules = rules) - parser.setContentHandler(t) - e = xml.sax.handler.ErrorHandler() - parser.setErrorHandler(e) - - inpsrc = xml.sax.xmlreader.InputSource() - inpsrc.setByteStream(StringIO(xml_str)) - - # turn on namespace mangeling - parser.setFeature(xml.sax.handler.feature_namespaces,1) - - parser.parse(inpsrc) - - return t - -################################################################################ -# SOAPParser's more public interface -################################################################################ -def parseSOAP(xml_str, attrs = 0): - t = _parseSOAP(xml_str) - - if attrs: - return t.body, t.attrs - return t.body - - -def parseSOAPRPC(xml_str, header = 0, body = 0, attrs = 0, rules = None): - t = _parseSOAP(xml_str, rules = rules) - p = t.body._aslist[0] - - # Empty string, for RPC this translates into a void - if type(p) in (type(''), type(u'')) and p in ('', u''): - name = "Response" - for k in t.body.__dict__.keys(): - if k[0] != "_": - name = k - p = structType(name) - - if header or body or attrs: - ret = (p,) - if header : ret += (t.header,) - if body: ret += (t.body,) - if attrs: ret += (t.attrs,) - return ret - else: - return p - - -################################################################################ -# SOAP Builder -################################################################################ -class SOAPBuilder: - _xml_top = '\n' - _xml_enc_top = '\n' - _env_top = '%(ENV_T)s:Envelope %(ENV_T)s:encodingStyle="%(ENC)s"' % \ - NS.__dict__ - _env_bot = '\n' % NS.__dict__ - - # Namespaces potentially defined in the Envelope tag. - - _env_ns = {NS.ENC: NS.ENC_T, NS.ENV: NS.ENV_T, - NS.XSD: NS.XSD_T, NS.XSD2: NS.XSD2_T, NS.XSD3: NS.XSD3_T, - NS.XSI: NS.XSI_T, NS.XSI2: NS.XSI2_T, NS.XSI3: NS.XSI3_T} - - def __init__(self, args = (), kw = {}, method = None, namespace = None, - header = None, methodattrs = None, envelope = 1, encoding = 'UTF-8', - use_refs = 0, config = Config): - - # Test the encoding, raising an exception if it's not known - if encoding != None: - ''.encode(encoding) - - self.args = args - self.kw = kw - self.envelope = envelope - self.encoding = encoding - self.method = method - self.namespace = namespace - self.header = header - self.methodattrs= methodattrs - self.use_refs = use_refs - self.config = config - self.out = '' - self.tcounter = 0 - self.ncounter = 1 - self.icounter = 1 - self.envns = {} - self.ids = {} - self.depth = 0 - self.multirefs = [] - self.multis = 0 - self.body = not isinstance(args, bodyType) - - def build(self): - ns_map = {} - - # Cache whether typing is on or not - typed = self.config.typed - - if self.header: - # Create a header. - self.dump(self.header, "Header", typed = typed) - self.header = None # Wipe it out so no one is using it. - if self.body: - # Call genns to record that we've used SOAP-ENV. - self.depth += 1 - body_ns = self.genns(ns_map, NS.ENV)[0] - self.out += "<%sBody>\n" % body_ns - - if self.method: - self.depth += 1 - a = '' - if self.methodattrs: - for (k, v) in self.methodattrs.items(): - a += ' %s="%s"' % (k, v) - - if self.namespace: # Use the namespace info handed to us - methodns, n = self.genns(ns_map, self.namespace) - else: - methodns, n = '', '' - - self.out += '<%s%s%s%s%s>\n' % \ - (methodns, self.method, n, a, self.genroot(ns_map)) - - try: - if type(self.args) != TupleType: - args = (self.args,) - else: - args = self.args - - for i in args: - self.dump(i, typed = typed, ns_map = ns_map) - - for (k, v) in self.kw.items(): - self.dump(v, k, typed = typed, ns_map = ns_map) - except RecursionError: - if self.use_refs == 0: - # restart - b = SOAPBuilder(args = self.args, kw = self.kw, - method = self.method, namespace = self.namespace, - header = self.header, methodattrs = self.methodattrs, - envelope = self.envelope, encoding = self.encoding, - use_refs = 1, config = self.config) - return b.build() - raise - - if self.method: - self.out += "\n" % (methodns, self.method) - self.depth -= 1 - - if self.body: - # dump may add to self.multirefs, but the for loop will keep - # going until it has used all of self.multirefs, even those - # entries added while in the loop. - - self.multis = 1 - - for obj, tag in self.multirefs: - self.dump(obj, tag, typed = typed, ns_map = ns_map) - - self.out += "\n" % body_ns - self.depth -= 1 - - if self.envelope: - e = map (lambda ns: 'xmlns:%s="%s"' % (ns[1], ns[0]), - self.envns.items()) - - self.out = '<' + self._env_top + ' '.join([''] + e) + '>\n' + \ - self.out + \ - self._env_bot - - if self.encoding != None: - self.out = self._xml_enc_top % self.encoding + self.out - - return self.out.encode(self.encoding) - - return self._xml_top + self.out - - def gentag(self): - self.tcounter += 1 - return "v%d" % self.tcounter - - def genns(self, ns_map, nsURI): - if nsURI == None: - return ('', '') - - if type(nsURI) == TupleType: # already a tuple - if len(nsURI) == 2: - ns, nsURI = nsURI - else: - ns, nsURI = None, nsURI[0] - else: - ns = None - - if ns_map.has_key(nsURI): - return (ns_map[nsURI] + ':', '') - - if self._env_ns.has_key(nsURI): - ns = self.envns[nsURI] = ns_map[nsURI] = self._env_ns[nsURI] - return (ns + ':', '') - - if not ns: - ns = "ns%d" % self.ncounter - self.ncounter += 1 - ns_map[nsURI] = ns - if self.config.buildWithNamespacePrefix: - return (ns + ':', ' xmlns:%s="%s"' % (ns, nsURI)) - else: - return ('', ' xmlns="%s"' % (nsURI)) - - def genroot(self, ns_map): - if self.depth != 2: - return '' - - ns, n = self.genns(ns_map, NS.ENC) - return ' %sroot="%d"%s' % (ns, not self.multis, n) - - # checkref checks an element to see if it needs to be encoded as a - # multi-reference element or not. If it returns None, the element has - # been handled and the caller can continue with subsequent elements. - # If it returns a string, the string should be included in the opening - # tag of the marshaled element. - - def checkref(self, obj, tag, ns_map): - if self.depth < 2: - return '' - - if not self.ids.has_key(id(obj)): - n = self.ids[id(obj)] = self.icounter - self.icounter = n + 1 - - if self.use_refs == 0: - return '' - - if self.depth == 2: - return ' id="i%d"' % n - - self.multirefs.append((obj, tag)) - else: - if self.use_refs == 0: - raise RecursionError, "Cannot serialize recursive object" - - n = self.ids[id(obj)] - - if self.multis and self.depth == 2: - return ' id="i%d"' % n - - self.out += '<%s href="#i%d"%s/>\n' % (tag, n, self.genroot(ns_map)) - return None - - # dumpers - - def dump(self, obj, tag = None, typed = 1, ns_map = {}): - ns_map = ns_map.copy() - self.depth += 1 - - if type(tag) not in (NoneType, StringType, UnicodeType): - raise KeyError, "tag must be a string or None" - - try: - meth = getattr(self, "dump_" + type(obj).__name__) - meth(obj, tag, typed, ns_map) - except AttributeError: - if type(obj) == LongType: - obj_type = "integer" - else: - obj_type = type(obj).__name__ - - self.out += self.dumper(None, obj_type, obj, tag, typed, - ns_map, self.genroot(ns_map)) - - self.depth -= 1 - - # generic dumper - def dumper(self, nsURI, obj_type, obj, tag, typed = 1, ns_map = {}, - rootattr = '', id = '', - xml = '<%(tag)s%(type)s%(id)s%(attrs)s%(root)s>%(data)s\n'): - - if nsURI == None: - nsURI = self.config.typesNamespaceURI - - tag = tag or self.gentag() - - a = n = t = '' - if typed and obj_type: - ns, n = self.genns(ns_map, nsURI) - ins = self.genns(ns_map, self.config.schemaNamespaceURI)[0] - t = ' %stype="%s%s"%s' % (ins, ns, obj_type, n) - - try: a = obj._marshalAttrs(ns_map, self) - except: pass - - try: data = obj._marshalData() - except: data = obj - - return xml % {"tag": tag, "type": t, "data": data, "root": rootattr, - "id": id, "attrs": a} - - def dump_float(self, obj, tag, typed = 1, ns_map = {}): - # Terrible windows hack - if not good_float: - if obj == float(1e300**2): - obj = "INF" - elif obj == float(-1e300**2): - obj = "-INF" - - obj = str(obj) - if obj in ('inf', '-inf'): - obj = str(obj).upper() - elif obj == 'nan': - obj = 'NaN' - self.out += self.dumper(None, "float", obj, tag, typed, ns_map, - self.genroot(ns_map)) - - def dump_string(self, obj, tag, typed = 0, ns_map = {}): - tag = tag or self.gentag() - - id = self.checkref(obj, tag, ns_map) - if id == None: - return - - try: data = obj._marshalData() - except: data = obj - - self.out += self.dumper(None, "string", cgi.escape(data), tag, - typed, ns_map, self.genroot(ns_map), id) - - dump_unicode = dump_string - dump_str = dump_string # 4/12/2002 - MAP - for Python 2.2 - - def dump_None(self, obj, tag, typed = 0, ns_map = {}): - tag = tag or self.gentag() - ns = self.genns(ns_map, self.config.schemaNamespaceURI)[0] - - self.out += '<%s %snull="1"%s/>\n' % (tag, ns, self.genroot(ns_map)) - - def dump_list(self, obj, tag, typed = 1, ns_map = {}): - if type(obj) == InstanceType: - data = obj.data - else: - data = obj - - tag = tag or self.gentag() - - id = self.checkref(obj, tag, ns_map) - if id == None: - return - - try: - sample = data[0] - empty = 0 - except: - sample = structType() - empty = 1 - - # First scan list to see if all are the same type - same_type = 1 - - if not empty: - for i in data[1:]: - if type(sample) != type(i) or \ - (type(sample) == InstanceType and \ - sample.__class__ != i.__class__): - same_type = 0 - break - - ndecl = '' - if same_type: - if (isinstance(sample, structType)) or \ - type(sample) == DictType: # force to urn struct - - try: - tns = obj._ns or NS.URN - except: - tns = NS.URN - - ns, ndecl = self.genns(ns_map, tns) - - try: - typename = last._typename - except: - typename = "SOAPStruct" - - t = ns + typename - - elif isinstance(sample, anyType): - ns = sample._validNamespaceURI(self.config.typesNamespaceURI, - self.config.strictNamespaces) - if ns: - ns, ndecl = self.genns(ns_map, ns) - t = ns + sample._type - else: - t = 'ur-type' - else: - t = self.genns(ns_map, self.config.typesNamespaceURI)[0] + \ - type(sample).__name__ - else: - t = self.genns(ns_map, self.config.typesNamespaceURI)[0] + \ - "ur-type" - - try: a = obj._marshalAttrs(ns_map, self) - except: a = '' - - ens, edecl = self.genns(ns_map, NS.ENC) - ins, idecl = self.genns(ns_map, self.config.schemaNamespaceURI) - - self.out += \ - '<%s %sarrayType="%s[%d]" %stype="%sArray"%s%s%s%s%s%s>\n' %\ - (tag, ens, t, len(data), ins, ens, ndecl, edecl, idecl, - self.genroot(ns_map), id, a) - - typed = not same_type - - try: elemsname = obj._elemsname - except: elemsname = "item" - - for i in data: - self.dump(i, elemsname, typed, ns_map) - - self.out += '\n' % tag - - dump_tuple = dump_list - - def dump_dictionary(self, obj, tag, typed = 1, ns_map = {}): - tag = tag or self.gentag() - - id = self.checkref(obj, tag, ns_map) - if id == None: - return - - try: a = obj._marshalAttrs(ns_map, self) - except: a = '' - - self.out += '<%s%s%s%s>\n' % \ - (tag, id, a, self.genroot(ns_map)) - - for (k, v) in obj.items(): - if k[0] != "_": - self.dump(v, k, 1, ns_map) - - self.out += '\n' % tag - dump_dict = dump_dictionary # 4/18/2002 - MAP - for Python 2.2 - - def dump_instance(self, obj, tag, typed = 1, ns_map = {}): - if not tag: - # If it has a name use it. - if isinstance(obj, anyType) and obj._name: - tag = obj._name - else: - tag = self.gentag() - - if isinstance(obj, arrayType): # Array - self.dump_list(obj, tag, typed, ns_map) - return - - if isinstance(obj, faultType): # Fault - cns, cdecl = self.genns(ns_map, NS.ENC) - vns, vdecl = self.genns(ns_map, NS.ENV) - self.out += '''<%sFault %sroot="1"%s%s> -%s -%s -''' % (vns, cns, vdecl, cdecl, obj.faultcode, obj.faultstring) - if hasattr(obj, "detail"): - self.dump(obj.detail, "detail", typed, ns_map) - self.out += "\n" % vns - return - - r = self.genroot(ns_map) - - try: a = obj._marshalAttrs(ns_map, self) - except: a = '' - - if isinstance(obj, voidType): # void - self.out += "<%s%s%s>\n" % (tag, a, r, tag) - return - - id = self.checkref(obj, tag, ns_map) - if id == None: - return - - if isinstance(obj, structType): - # Check for namespace - ndecl = '' - ns = obj._validNamespaceURI(self.config.typesNamespaceURI, - self.config.strictNamespaces) - if ns: - ns, ndecl = self.genns(ns_map, ns) - tag = ns + tag - self.out += "<%s%s%s%s%s>\n" % (tag, ndecl, id, a, r) - - # If we have order use it. - order = 1 - - for i in obj._keys(): - if i not in obj._keyord: - order = 0 - break - if order: - for i in range(len(obj._keyord)): - self.dump(obj._aslist[i], obj._keyord[i], 1, ns_map) - else: - # don't have pristine order information, just build it. - for (k, v) in obj.__dict__.items(): - if k[0] != "_": - self.dump(v, k, 1, ns_map) - - if isinstance(obj, bodyType): - self.multis = 1 - - for v, k in self.multirefs: - self.dump(v, k, typed = typed, ns_map = ns_map) - - self.out += '\n' % tag - - elif isinstance(obj, anyType): - t = '' - - if typed: - ns = obj._validNamespaceURI(self.config.typesNamespaceURI, - self.config.strictNamespaces) - if ns: - ons, ondecl = self.genns(ns_map, ns) - ins, indecl = self.genns(ns_map, - self.config.schemaNamespaceURI) - t = ' %stype="%s%s"%s%s' % \ - (ins, ons, obj._type, ondecl, indecl) - - self.out += '<%s%s%s%s%s>%s\n' % \ - (tag, t, id, a, r, obj._marshalData(), tag) - - else: # Some Class - self.out += '<%s%s%s>\n' % (tag, id, r) - - for (k, v) in obj.__dict__.items(): - if k[0] != "_": - self.dump(v, k, 1, ns_map) - - self.out += '\n' % tag - - -################################################################################ -# SOAPBuilder's more public interface -################################################################################ -def buildSOAP(args=(), kw={}, method=None, namespace=None, header=None, - methodattrs=None,envelope=1,encoding='UTF-8',config=Config): - t = SOAPBuilder(args=args,kw=kw, method=method, namespace=namespace, - header=header, methodattrs=methodattrs,envelope=envelope, - encoding=encoding, config=config) - return t.build() - -################################################################################ -# RPC -################################################################################ - -def SOAPUserAgent(): - return "SOAP.py " + __version__ + " (actzero.com)" - -################################################################################ -# Client -################################################################################ -class SOAPAddress: - def __init__(self, url, config = Config): - proto, uri = urllib.splittype(url) - - # apply some defaults - if uri[0:2] != '//': - if proto != None: - uri = proto + ':' + uri - - uri = '//' + uri - proto = 'http' - - host, path = urllib.splithost(uri) - - try: - int(host) - host = 'localhost:' + host - except: - pass - - if not path: - path = '/' - - if proto not in ('http', 'https'): - raise IOError, "unsupported SOAP protocol" - if proto == 'https' and not config.SSLclient: - raise AttributeError, \ - "SSL client not supported by this Python installation" - - self.proto = proto - self.host = host - self.path = path - - def __str__(self): - return "%(proto)s://%(host)s%(path)s" % self.__dict__ - - __repr__ = __str__ - - -class HTTPTransport: - # Need a Timeout someday? - def call(self, addr, data, soapaction = '', encoding = None, - http_proxy = None, config = Config): - - import httplib - - if not isinstance(addr, SOAPAddress): - addr = SOAPAddress(addr, config) - - # Build a request - if http_proxy: - real_addr = http_proxy - real_path = addr.proto + "://" + addr.host + addr.path - else: - real_addr = addr.host - real_path = addr.path - - if addr.proto == 'https': - r = httplib.HTTPS(real_addr) - else: - r = httplib.HTTP(real_addr) - - r.putrequest("POST", real_path) - - r.putheader("Host", addr.host) - r.putheader("User-agent", SOAPUserAgent()) - t = 'text/xml'; - if encoding != None: - t += '; charset="%s"' % encoding - r.putheader("Content-type", t) - r.putheader("Content-length", str(len(data))) - r.putheader("SOAPAction", '"%s"' % soapaction) - - if config.dumpHeadersOut: - s = 'Outgoing HTTP headers' - debugHeader(s) - print "POST %s %s" % (real_path, r._http_vsn_str) - print "Host:", addr.host - print "User-agent: SOAP.py " + __version__ + " (actzero.com)" - print "Content-type:", t - print "Content-length:", len(data) - print 'SOAPAction: "%s"' % soapaction - debugFooter(s) - - r.endheaders() - - if config.dumpSOAPOut: - s = 'Outgoing SOAP' - debugHeader(s) - print data, - if data[-1] != '\n': - print - debugFooter(s) - - # send the payload - r.send(data) - - # read response line - code, msg, headers = r.getreply() - - if config.dumpHeadersIn: - s = 'Incoming HTTP headers' - debugHeader(s) - if headers.headers: - print "HTTP/1.? %d %s" % (code, msg) - print "\n".join(map (lambda x: x.strip(), headers.headers)) - else: - print "HTTP/0.9 %d %s" % (code, msg) - debugFooter(s) - - if config.dumpSOAPIn: - data = r.getfile().read() - - s = 'Incoming SOAP' - debugHeader(s) - print data, - if data[-1] != '\n': - print - debugFooter(s) - - if code not in (200, 500): - raise HTTPError(code, msg) - - if not config.dumpSOAPIn: - data = r.getfile().read() - - # return response payload - return data - -################################################################################ -# SOAP Proxy -################################################################################ -class SOAPProxy: - def __init__(self, proxy, namespace = None, soapaction = '', - header = None, methodattrs = None, transport = HTTPTransport, - encoding = 'UTF-8', throw_faults = 1, unwrap_results = 1, - http_proxy=None, config = Config): - - # Test the encoding, raising an exception if it's not known - if encoding != None: - ''.encode(encoding) - - self.proxy = SOAPAddress(proxy, config) - self.namespace = namespace - self.soapaction = soapaction - self.header = header - self.methodattrs = methodattrs - self.transport = transport() - self.encoding = encoding - self.throw_faults = throw_faults - self.unwrap_results = unwrap_results - self.http_proxy = http_proxy - self.config = config - - - def __call(self, name, args, kw, ns = None, sa = None, hd = None, - ma = None): - - ns = ns or self.namespace - ma = ma or self.methodattrs - - if sa: # Get soapaction - if type(sa) == TupleType: sa = sa[0] - else: - sa = self.soapaction - - if hd: # Get header - if type(hd) == TupleType: - hd = hd[0] - else: - hd = self.header - - hd = hd or self.header - - if ma: # Get methodattrs - if type(ma) == TupleType: ma = ma[0] - else: - ma = self.methodattrs - ma = ma or self.methodattrs - - m = buildSOAP(args = args, kw = kw, method = name, namespace = ns, - header = hd, methodattrs = ma, encoding = self.encoding, - config = self.config) - - r = self.transport.call(self.proxy, m, sa, encoding = self.encoding, - http_proxy = self.http_proxy, - config = self.config) - - p, attrs = parseSOAPRPC(r, attrs = 1) - - try: - throw_struct = self.throw_faults and \ - isinstance (p, faultType) - except: - throw_struct = 0 - - if throw_struct: - raise p - - # Bubble a regular result up, if there is only element in the - # struct, assume that is the result and return it. - # Otherwise it will return the struct with all the elements - # as attributes. - if self.unwrap_results: - try: - count = 0 - for i in p.__dict__.keys(): - if i[0] != "_": # don't move the private stuff - count += 1 - t = getattr(p, i) - if count == 1: p = t # Only one piece of data, bubble it up - except: - pass - - if self.config.returnAllAttrs: - return p, attrs - return p - - def _callWithBody(self, body): - return self.__call(None, body, {}) - - def __getattr__(self, name): # hook to catch method calls - if name == '__del__': - raise AttributeError, name - else: - return self.__Method(self.__call, name, config = self.config) - - # To handle attribute wierdness - class __Method: - # Some magic to bind a SOAP method to an RPC server. - # Supports "nested" methods (e.g. examples.getStateName) -- concept - # borrowed from xmlrpc/soaplib -- www.pythonware.com - # Altered (improved?) to let you inline namespaces on a per call - # basis ala SOAP::LITE -- www.soaplite.com - - def __init__(self, call, name, ns = None, sa = None, hd = None, - ma = None, config = Config): - - self.__call = call - self.__name = name - self.__ns = ns - self.__sa = sa - self.__hd = hd - self.__ma = ma - self.__config = config - if self.__name[0] == "_": - if self.__name in ["__repr__","__str__"]: - self.__call__ = self.__repr__ - else: - self.__call__ = self.__f_call - else: - self.__call__ = self.__r_call - - def __getattr__(self, name): - if name == '__del__': - raise AttributeError, name - if self.__name[0] == "_": - # Don't nest method if it is a directive - return self.__class__(self.__call, name, self.__ns, - self.__sa, self.__hd, self.__ma) - - return self.__class__(self.__call, "%s.%s" % (self.__name, name), - self.__ns, self.__sa, self.__hd, self.__ma) - - def __f_call(self, *args, **kw): - if self.__name == "_ns": self.__ns = args - elif self.__name == "_sa": self.__sa = args - elif self.__name == "_hd": self.__hd = args - elif self.__name == "_ma": self.__ma = args - return self - - def __r_call(self, *args, **kw): - return self.__call(self.__name, args, kw, self.__ns, self.__sa, - self.__hd, self.__ma) - - def __repr__(self): - return "<%s at %d>" % (self.__class__, id(self)) - -################################################################################ -# Server -################################################################################ - -# Method Signature class for adding extra info to registered funcs, right now -# used just to indicate it should be called with keywords, instead of ordered -# params. -class MethodSig: - def __init__(self, func, keywords=0, context=0): - self.func = func - self.keywords = keywords - self.context = context - self.__name__ = func.__name__ - - def __call__(self, *args, **kw): - return apply(self.func,args,kw) - -class SOAPContext: - def __init__(self, header, body, attrs, xmldata, connection, httpheaders, - soapaction): - - self.header = header - self.body = body - self.attrs = attrs - self.xmldata = xmldata - self.connection = connection - self.httpheaders= httpheaders - self.soapaction = soapaction - -# A class to describe how header messages are handled -class HeaderHandler: - # Initially fail out if there are any problems. - def __init__(self, header, attrs): - for i in header.__dict__.keys(): - if i[0] == "_": - continue - - d = getattr(header, i) - - try: - fault = int(attrs[id(d)][(NS.ENV, 'mustUnderstand')]) - except: - fault = 0 - - if fault: - raise faultType, ("%s:MustUnderstand" % NS.ENV_T, - "Don't understand `%s' header element but " - "mustUnderstand attribute is set." % i) - - -################################################################################ -# SOAP Server -################################################################################ -class SOAPServer(SocketServer.TCPServer): - import BaseHTTPServer - - class SOAPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): - def version_string(self): - return '' + \ - 'SOAP.py ' + __version__ + ' (Python ' + \ - sys.version.split()[0] + ')' - - def date_time_string(self): - self.__last_date_time_string = \ - SOAPServer.BaseHTTPServer.BaseHTTPRequestHandler.\ - date_time_string(self) - - return self.__last_date_time_string - - def do_POST(self): - try: - if self.server.config.dumpHeadersIn: - s = 'Incoming HTTP headers' - debugHeader(s) - print self.raw_requestline.strip() - print "\n".join(map (lambda x: x.strip(), - self.headers.headers)) - debugFooter(s) - - data = self.rfile.read(int(self.headers["content-length"])) - - if self.server.config.dumpSOAPIn: - s = 'Incoming SOAP' - debugHeader(s) - print data, - if data[-1] != '\n': - print - debugFooter(s) - - (r, header, body, attrs) = \ - parseSOAPRPC(data, header = 1, body = 1, attrs = 1) - - method = r._name - args = r._aslist - kw = r._asdict - - ns = r._ns - resp = "" - # For fault messages - if ns: - nsmethod = "%s:%s" % (ns, method) - else: - nsmethod = method - - try: - # First look for registered functions - if self.server.funcmap.has_key(ns) and \ - self.server.funcmap[ns].has_key(method): - f = self.server.funcmap[ns][method] - else: # Now look at registered objects - # Check for nested attributes. This works even if - # there are none, because the split will return - # [method] - f = self.server.objmap[ns] - l = method.split(".") - for i in l: - f = getattr(f, i) - except: - resp = buildSOAP(faultType("%s:Client" % NS.ENV_T, - "No method %s found" % nsmethod, - "%s %s" % tuple(sys.exc_info()[0:2])), - encoding = self.server.encoding, - config = self.server.config) - status = 500 - else: - try: - if header: - x = HeaderHandler(header, attrs) - - # If it's wrapped, some special action may be needed - - if isinstance(f, MethodSig): - c = None - - if f.context: # Build context object - c = SOAPContext(header, body, attrs, data, - self.connection, self.headers, - self.headers["soapaction"]) - - if f.keywords: - # This is lame, but have to de-unicode - # keywords - - strkw = {} - - for (k, v) in kw.items(): - strkw[str(k)] = v - if c: - strkw["_SOAPContext"] = c - fr = apply(f, (), strkw) - elif c: - fr = apply(f, args, {'_SOAPContext':c}) - else: - fr = apply(f, args, {}) - else: - fr = apply(f, args, {}) - - if type(fr) == type(self) and \ - isinstance(fr, voidType): - resp = buildSOAP(kw = {'%sResponse' % method: fr}, - encoding = self.server.encoding, - config = self.server.config) - else: - resp = buildSOAP(kw = - {'%sResponse' % method: {'Result': fr}}, - encoding = self.server.encoding, - config = self.server.config) - except Exception, e: - import traceback - info = sys.exc_info() - - if self.server.config.dumpFaultInfo: - s = 'Method %s exception' % nsmethod - debugHeader(s) - traceback.print_exception(info[0], info[1], - info[2]) - debugFooter(s) - - if isinstance(e, faultType): - f = e - else: - f = faultType("%s:Server" % NS.ENV_T, - "Method %s failed." % nsmethod) - - if self.server.config.returnFaultInfo: - f._setDetail("".join(traceback.format_exception( - info[0], info[1], info[2]))) - elif not hasattr(f, 'detail'): - f._setDetail("%s %s" % (info[0], info[1])) - - resp = buildSOAP(f, encoding = self.server.encoding, - config = self.server.config) - status = 500 - else: - status = 200 - except faultType, e: - import traceback - info = sys.exc_info() - - if self.server.config.dumpFaultInfo: - s = 'Received fault exception' - debugHeader(s) - traceback.print_exception(info[0], info[1], - info[2]) - debugFooter(s) - - if self.server.config.returnFaultInfo: - e._setDetail("".join(traceback.format_exception( - info[0], info[1], info[2]))) - elif not hasattr(e, 'detail'): - e._setDetail("%s %s" % (info[0], info[1])) - - resp = buildSOAP(e, encoding = self.server.encoding, - config = self.server.config) - status = 500 - except: - # internal error, report as HTTP server error - if self.server.config.dumpFaultInfo: - import traceback - s = 'Internal exception' - debugHeader(s) - traceback.print_exc () - debugFooter(s) - self.send_response(500) - self.end_headers() - - if self.server.config.dumpHeadersOut and \ - self.request_version != 'HTTP/0.9': - s = 'Outgoing HTTP headers' - debugHeader(s) - if self.responses.has_key(status): - s = ' ' + self.responses[status][0] - else: - s = '' - print "%s %d%s" % (self.protocol_version, 500, s) - print "Server:", self.version_string() - print "Date:", self.__last_date_time_string - debugFooter(s) - else: - # got a valid SOAP response - self.send_response(status) - - t = 'text/xml'; - if self.server.encoding != None: - t += '; charset="%s"' % self.server.encoding - self.send_header("Content-type", t) - self.send_header("Content-length", str(len(resp))) - self.end_headers() - - if self.server.config.dumpHeadersOut and \ - self.request_version != 'HTTP/0.9': - s = 'Outgoing HTTP headers' - debugHeader(s) - if self.responses.has_key(status): - s = ' ' + self.responses[status][0] - else: - s = '' - print "%s %d%s" % (self.protocol_version, status, s) - print "Server:", self.version_string() - print "Date:", self.__last_date_time_string - print "Content-type:", t - print "Content-length:", len(resp) - debugFooter(s) - - if self.server.config.dumpSOAPOut: - s = 'Outgoing SOAP' - debugHeader(s) - print resp, - if resp[-1] != '\n': - print - debugFooter(s) - - self.wfile.write(resp) - self.wfile.flush() - - # We should be able to shut down both a regular and an SSL - # connection, but under Python 2.1, calling shutdown on an - # SSL connections drops the output, so this work-around. - # This should be investigated more someday. - - if self.server.config.SSLserver and \ - isinstance(self.connection, SSL.Connection): - self.connection.set_shutdown(SSL.SSL_SENT_SHUTDOWN | - SSL.SSL_RECEIVED_SHUTDOWN) - else: - self.connection.shutdown(1) - - def log_message(self, format, *args): - if self.server.log: - SOAPServer.BaseHTTPServer.BaseHTTPRequestHandler.\ - log_message (self, format, *args) - - def __init__(self, addr = ('localhost', 8000), - RequestHandler = SOAPRequestHandler, log = 1, encoding = 'UTF-8', - config = Config, namespace = None, ssl_context = None): - - # Test the encoding, raising an exception if it's not known - if encoding != None: - ''.encode(encoding) - - if ssl_context != None and not config.SSLserver: - raise AttributeError, \ - "SSL server not supported by this Python installation" - - self.namespace = namespace - self.objmap = {} - self.funcmap = {} - self.ssl_context = ssl_context - self.encoding = encoding - self.config = config - self.log = log - - self.allow_reuse_address= 1 - - SocketServer.TCPServer.__init__(self, addr, RequestHandler) - - def get_request(self): - sock, addr = SocketServer.TCPServer.get_request(self) - - if self.ssl_context: - sock = SSL.Connection(self.ssl_context, sock) - sock._setup_ssl(addr) - if sock.accept_ssl() != 1: - raise socket.error, "Couldn't accept SSL connection" - - return sock, addr - - def registerObject(self, object, namespace = ''): - if namespace == '': namespace = self.namespace - self.objmap[namespace] = object - - def registerFunction(self, function, namespace = '', funcName = None): - if not funcName : funcName = function.__name__ - if namespace == '': namespace = self.namespace - if self.funcmap.has_key(namespace): - self.funcmap[namespace][funcName] = function - else: - self.funcmap[namespace] = {funcName : function} - - def registerKWObject(self, object, namespace = ''): - if namespace == '': namespace = self.namespace - for i in dir(object.__class__): - if i[0] != "_" and callable(getattr(object, i)): - self.registerKWFunction(getattr(object,i), namespace) - - # convenience - wraps your func for you. - def registerKWFunction(self, function, namespace = '', funcName = None): - self.registerFunction(MethodSig(function,keywords=1), namespace, - funcName) -# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78: +#!/usr/bin/python +################################################################################ +# +# SOAP.py 0.9.7 - Cayce Ullman (cayce@actzero.com) +# Brian Matthews (blm@actzero.com) +# +# INCLUDED: +# - General SOAP Parser based on sax.xml (requires Python 2.0) +# - General SOAP Builder +# - SOAP Proxy for RPC client code +# - SOAP Server framework for RPC server code +# +# FEATURES: +# - Handles all of the types in the BDG +# - Handles faults +# - Allows namespace specification +# - Allows SOAPAction specification +# - Homogeneous typed arrays +# - Supports multiple schemas +# - Header support (mustUnderstand and actor) +# - XML attribute support +# - Multi-referencing support (Parser/Builder) +# - Understands SOAP-ENC:root attribute +# - Good interop, passes all client tests for Frontier, SOAP::LITE, SOAPRMI +# - Encodings +# - SSL clients (with OpenSSL configured in to Python) +# - SSL servers (with OpenSSL configured in to Python and M2Crypto installed) +# +# TODO: +# - Timeout on method calls - MCU +# - Arrays (sparse, multidimensional and partial) - BLM +# - Clean up data types - BLM +# - Type coercion system (Builder) - MCU +# - Early WSDL Support - MCU +# - Attachments - BLM +# - setup.py - MCU +# - mod_python example - MCU +# - medusa example - MCU +# - Documentation - JAG +# - Look at performance +# +################################################################################ +# +# Copyright (c) 2001, Cayce Ullman. +# Copyright (c) 2001, Brian Matthews. +# +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# Neither the name of actzero, inc. nor the names of its contributors may +# be used to endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR +# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +################################################################################ +# +# Additional changes: +# 0.9.7.3 - 4/18/2002 - Mark Pilgrim (f8dy@diveintomark.org) +# added dump_dict as alias for dump_dictionary for Python 2.2 compatibility +# 0.9.7.2 - 4/12/2002 - Mark Pilgrim (f8dy@diveintomark.org) +# fixed logic to unmarshal the value of "null" attributes ("true" or "1" +# means true, others false) +# 0.9.7.1 - 4/11/2002 - Mark Pilgrim (f8dy@diveintomark.org) +# added "dump_str" as alias for "dump_string" for Python 2.2 compatibility +# Between 2.1 and 2.2, type("").__name__ changed from "string" to "str" +################################################################################ + +import xml.sax +import UserList +import base64 +import cgi +import urllib +import exceptions +import copy +import re +import socket +import string +import sys +import time +import SocketServer +from types import * + +try: from M2Crypto import SSL +except: pass + +ident = '$Id$' + +__version__ = "0.9.7.3" + +# Platform hackery + +# Check float support +try: + float("NaN") + float("INF") + float("-INF") + good_float = 1 +except: + good_float = 0 + +################################################################################ +# Exceptions +################################################################################ +class Error(exceptions.Exception): + def __init__(self, msg): + self.msg = msg + def __str__(self): + return "" % self.msg + __repr__ = __str__ + +class RecursionError(Error): + pass + +class UnknownTypeError(Error): + pass + +class HTTPError(Error): + # indicates an HTTP protocol error + def __init__(self, code, msg): + self.code = code + self.msg = msg + def __str__(self): + return "" % (self.code, self.msg) + __repr__ = __str__ + +############################################################################## +# Namespace Class +################################################################################ +def invertDict(dict): + d = {} + + for k, v in dict.items(): + d[v] = k + + return d + +class NS: + XML = "http://www.w3.org/XML/1998/namespace" + + ENV = "http://schemas.xmlsoap.org/soap/envelope/" + ENC = "http://schemas.xmlsoap.org/soap/encoding/" + + XSD = "http://www.w3.org/1999/XMLSchema" + XSD2 = "http://www.w3.org/2000/10/XMLSchema" + XSD3 = "http://www.w3.org/2001/XMLSchema" + + XSD_L = [XSD, XSD2, XSD3] + EXSD_L= [ENC, XSD, XSD2, XSD3] + + XSI = "http://www.w3.org/1999/XMLSchema-instance" + XSI2 = "http://www.w3.org/2000/10/XMLSchema-instance" + XSI3 = "http://www.w3.org/2001/XMLSchema-instance" + XSI_L = [XSI, XSI2, XSI3] + + URN = "http://soapinterop.org/xsd" + + # For generated messages + XML_T = "xml" + ENV_T = "SOAP-ENV" + ENC_T = "SOAP-ENC" + XSD_T = "xsd" + XSD2_T= "xsd2" + XSD3_T= "xsd3" + XSI_T = "xsi" + XSI2_T= "xsi2" + XSI3_T= "xsi3" + URN_T = "urn" + + NSMAP = {ENV_T: ENV, ENC_T: ENC, XSD_T: XSD, XSD2_T: XSD2, + XSD3_T: XSD3, XSI_T: XSI, XSI2_T: XSI2, XSI3_T: XSI3, + URN_T: URN} + NSMAP_R = invertDict(NSMAP) + + STMAP = {'1999': (XSD_T, XSI_T), '2000': (XSD2_T, XSI2_T), + '2001': (XSD3_T, XSI3_T)} + STMAP_R = invertDict(STMAP) + + def __init__(self): + raise Error, "Don't instantiate this" + +################################################################################ +# Configuration class +################################################################################ + +class SOAPConfig: + __readonly = ('SSLserver', 'SSLclient') + + def __init__(self, config = None, **kw): + d = self.__dict__ + + if config: + if not isinstance(config, SOAPConfig): + raise AttributeError, \ + "initializer must be SOAPConfig instance" + + s = config.__dict__ + + for k, v in s.items(): + if k[0] != '_': + d[k] = v + else: + # Setting debug also sets returnFaultInfo, dumpFaultInfo, + # dumpHeadersIn, dumpHeadersOut, dumpSOAPIn, and dumpSOAPOut + self.debug = 0 + # Setting namespaceStyle sets typesNamespace, typesNamespaceURI, + # schemaNamespace, and schemaNamespaceURI + self.namespaceStyle = '1999' + self.strictNamespaces = 0 + self.typed = 1 + self.buildWithNamespacePrefix = 1 + self.returnAllAttrs = 0 + + try: SSL; d['SSLserver'] = 1 + except: d['SSLserver'] = 0 + + try: socket.ssl; d['SSLclient'] = 1 + except: d['SSLclient'] = 0 + + for k, v in kw.items(): + if k[0] != '_': + setattr(self, k, v) + + def __setattr__(self, name, value): + if name in self.__readonly: + raise AttributeError, "readonly configuration setting" + + d = self.__dict__ + + if name in ('typesNamespace', 'typesNamespaceURI', + 'schemaNamespace', 'schemaNamespaceURI'): + + if name[-3:] == 'URI': + base, uri = name[:-3], 1 + else: + base, uri = name, 0 + + if type(value) == StringType: + if NS.NSMAP.has_key(value): + n = (value, NS.NSMAP[value]) + elif NS.NSMAP_R.has_key(value): + n = (NS.NSMAP_R[value], value) + else: + raise AttributeError, "unknown namespace" + elif type(value) in (ListType, TupleType): + if uri: + n = (value[1], value[0]) + else: + n = (value[0], value[1]) + else: + raise AttributeError, "unknown namespace type" + + d[base], d[base + 'URI'] = n + + try: + d['namespaceStyle'] = \ + NS.STMAP_R[(d['typesNamespace'], d['schemaNamespace'])] + except: + d['namespaceStyle'] = '' + + elif name == 'namespaceStyle': + value = str(value) + + if not NS.STMAP.has_key(value): + raise AttributeError, "unknown namespace style" + + d[name] = value + n = d['typesNamespace'] = NS.STMAP[value][0] + d['typesNamespaceURI'] = NS.NSMAP[n] + n = d['schemaNamespace'] = NS.STMAP[value][1] + d['schemaNamespaceURI'] = NS.NSMAP[n] + + elif name == 'debug': + d[name] = \ + d['returnFaultInfo'] = \ + d['dumpFaultInfo'] = \ + d['dumpHeadersIn'] = \ + d['dumpHeadersOut'] = \ + d['dumpSOAPIn'] = \ + d['dumpSOAPOut'] = value + + else: + d[name] = value + +Config = SOAPConfig() + +################################################################################ +# Types and Wrappers +################################################################################ + +class anyType: + _validURIs = (NS.XSD, NS.XSD2, NS.XSD3, NS.ENC) + + def __init__(self, data = None, name = None, typed = 1, attrs = None): + if self.__class__ == anyType: + raise Error, "anyType can't be instantiated directly" + + if type(name) in (ListType, TupleType): + self._ns, self._name = name + else: + self._ns, self._name = self._validURIs[0], name + self._typed = typed + self._attrs = {} + + self._cache = None + self._type = self._typeName() + + self._data = self._checkValueSpace(data) + + if attrs != None: + self._setAttrs(attrs) + + def __str__(self): + if self._name: + return "<%s %s at %d>" % (self.__class__, self._name, id(self)) + return "<%s at %d>" % (self.__class__, id(self)) + + __repr__ = __str__ + + def _checkValueSpace(self, data): + return data + + def _marshalData(self): + return str(self._data) + + def _marshalAttrs(self, ns_map, builder): + a = '' + + for attr, value in self._attrs.items(): + ns, n = builder.genns(ns_map, attr[0]) + a += n + ' %s%s="%s"' % \ + (ns, attr[1], cgi.escape(str(value), 1)) + + return a + + def _fixAttr(self, attr): + if type(attr) in (StringType, UnicodeType): + attr = (None, attr) + elif type(attr) == ListType: + attr = tuple(attr) + elif type(attr) != TupleType: + raise AttributeError, "invalid attribute type" + + if len(attr) != 2: + raise AttributeError, "invalid attribute length" + + if type(attr[0]) not in (NoneType, StringType, UnicodeType): + raise AttributeError, "invalid attribute namespace URI type" + + return attr + + def _getAttr(self, attr): + attr = self._fixAttr(attr) + + try: + return self._attrs[attr] + except: + return None + + def _setAttr(self, attr, value): + attr = self._fixAttr(attr) + + self._attrs[attr] = str(value) + + def _setAttrs(self, attrs): + if type(attrs) in (ListType, TupleType): + for i in range(0, len(attrs), 2): + self._setAttr(attrs[i], attrs[i + 1]) + + return + + if type(attrs) == DictType: + d = attrs + elif isinstance(attrs, anyType): + d = attrs._attrs + else: + raise AttributeError, "invalid attribute type" + + for attr, value in d.items(): + self._setAttr(attr, value) + + def _setMustUnderstand(self, val): + self._setAttr((NS.ENV, "mustUnderstand"), val) + + def _getMustUnderstand(self): + return self._getAttr((NS.ENV, "mustUnderstand")) + + def _setActor(self, val): + self._setAttr((NS.ENV, "actor"), val) + + def _getActor(self): + return self._getAttr((NS.ENV, "actor")) + + def _typeName(self): + return self.__class__.__name__[:-4] + + def _validNamespaceURI(self, URI, strict): + if not self._typed: + return None + if URI in self._validURIs: + return URI + if not strict: + return self._ns + raise AttributeError, \ + "not a valid namespace for type %s" % self._type + +class voidType(anyType): + pass + +class stringType(anyType): + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + if type(data) not in (StringType, UnicodeType): + raise AttributeError, "invalid %s type" % self._type + + return data + +class untypedType(stringType): + def __init__(self, data = None, name = None, attrs = None): + stringType.__init__(self, data, name, 0, attrs) + +class IDType(stringType): pass +class NCNameType(stringType): pass +class NameType(stringType): pass +class ENTITYType(stringType): pass +class IDREFType(stringType): pass +class languageType(stringType): pass +class NMTOKENType(stringType): pass +class QNameType(stringType): pass + +class tokenType(anyType): + _validURIs = (NS.XSD2, NS.XSD3) + __invalidre = '[\n\t]|^ | $| ' + + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + if type(data) not in (StringType, UnicodeType): + raise AttributeError, "invalid %s type" % self._type + + if type(self.__invalidre) == StringType: + self.__invalidre = re.compile(self.__invalidre) + + if self.__invalidre.search(data): + raise ValueError, "invalid %s value" % self._type + + return data + +class normalizedStringType(anyType): + _validURIs = (NS.XSD3,) + __invalidre = '[\n\r\t]' + + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + if type(data) not in (StringType, UnicodeType): + raise AttributeError, "invalid %s type" % self._type + + if type(self.__invalidre) == StringType: + self.__invalidre = re.compile(self.__invalidre) + + if self.__invalidre.search(data): + raise ValueError, "invalid %s value" % self._type + + return data + +class CDATAType(normalizedStringType): + _validURIs = (NS.XSD2,) + +class booleanType(anyType): + def __int__(self): + return self._data + + __nonzero__ = __int__ + + def _marshalData(self): + return ['false', 'true'][self._data] + + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + if data in (0, '0', 'false', ''): + return 0 + if data in (1, '1', 'true'): + return 1 + raise ValueError, "invalid %s value" % self._type + +class decimalType(anyType): + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + if type(data) not in (IntType, LongType, FloatType): + raise Error, "invalid %s value" % self._type + + return data + +class floatType(anyType): + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + if type(data) not in (IntType, LongType, FloatType) or \ + data < -3.4028234663852886E+38 or \ + data > 3.4028234663852886E+38: + raise ValueError, "invalid %s value" % self._type + + return data + + def _marshalData(self): + return "%.18g" % self._data # More precision + +class doubleType(anyType): + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + if type(data) not in (IntType, LongType, FloatType) or \ + data < -1.7976931348623158E+308 or \ + data > 1.7976931348623157E+308: + raise ValueError, "invalid %s value" % self._type + + return data + + def _marshalData(self): + return "%.18g" % self._data # More precision + +class durationType(anyType): + _validURIs = (NS.XSD3,) + + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + try: + # A tuple or a scalar is OK, but make them into a list + + if type(data) == TupleType: + data = list(data) + elif type(data) != ListType: + data = [data] + + if len(data) > 6: + raise Exception, "too many values" + + # Now check the types of all the components, and find + # the first nonzero element along the way. + + f = -1 + + for i in range(len(data)): + if data[i] == None: + data[i] = 0 + continue + + if type(data[i]) not in \ + (IntType, LongType, FloatType): + raise Exception, "element %d a bad type" % i + + if data[i] and f == -1: + f = i + + # If they're all 0, just use zero seconds. + + if f == -1: + self._cache = 'PT0S' + + return (0,) * 6 + + # Make sure only the last nonzero element has a decimal fraction + # and only the first element is negative. + + d = -1 + + for i in range(f, len(data)): + if data[i]: + if d != -1: + raise Exception, \ + "all except the last nonzero element must be " \ + "integers" + if data[i] < 0 and i > f: + raise Exception, \ + "only the first nonzero element can be negative" + elif data[i] != long(data[i]): + d = i + + # Pad the list on the left if necessary. + + if len(data) < 6: + n = 6 - len(data) + f += n + d += n + data = [0] * n + data + + # Save index of the first nonzero element and the decimal + # element for _marshalData. + + self.__firstnonzero = f + self.__decimal = d + + except Exception, e: + raise ValueError, "invalid %s value - %s" % (self._type, e) + + return tuple(data) + + def _marshalData(self): + if self._cache == None: + d = self._data + t = 0 + + if d[self.__firstnonzero] < 0: + s = '-P' + else: + s = 'P' + + t = 0 + + for i in range(self.__firstnonzero, len(d)): + if d[i]: + if i > 2 and not t: + s += 'T' + t = 1 + if self.__decimal == i: + s += "%g" % abs(d[i]) + else: + s += "%d" % long(abs(d[i])) + s += ['Y', 'M', 'D', 'H', 'M', 'S'][i] + + self._cache = s + + return self._cache + +class timeDurationType(durationType): + _validURIs = (NS.XSD, NS.XSD2, NS.ENC) + +class dateTimeType(anyType): + _validURIs = (NS.XSD3,) + + def _checkValueSpace(self, data): + try: + if data == None: + data = time.time() + + if (type(data) in (IntType, LongType)): + data = list(time.gmtime(data)[:6]) + elif (type(data) == FloatType): + f = data - int(data) + data = list(time.gmtime(int(data))[:6]) + data[5] += f + elif type(data) in (ListType, TupleType): + if len(data) < 6: + raise Exception, "not enough values" + if len(data) > 9: + raise Exception, "too many values" + + data = list(data[:6]) + + cleanDate(data) + else: + raise Exception, "invalid type" + except Exception, e: + raise ValueError, "invalid %s value - %s" % (self._type, e) + + return tuple(data) + + def _marshalData(self): + if self._cache == None: + d = self._data + s = "%04d-%02d-%02dT%02d:%02d:%02d" % ((abs(d[0]),) + d[1:]) + if d[0] < 0: + s = '-' + s + f = d[5] - int(d[5]) + if f != 0: + s += ("%g" % f)[1:] + s += 'Z' + + self._cache = s + + return self._cache + +class recurringInstantType(anyType): + _validURIs = (NS.XSD,) + + def _checkValueSpace(self, data): + try: + if data == None: + data = list(time.gmtime(time.time())[:6]) + if (type(data) in (IntType, LongType)): + data = list(time.gmtime(data)[:6]) + elif (type(data) == FloatType): + f = data - int(data) + data = list(time.gmtime(int(data))[:6]) + data[5] += f + elif type(data) in (ListType, TupleType): + if len(data) < 1: + raise Exception, "not enough values" + if len(data) > 9: + raise Exception, "too many values" + + data = list(data[:6]) + + if len(data) < 6: + data += [0] * (6 - len(data)) + + f = len(data) + + for i in range(f): + if data[i] == None: + if f < i: + raise Exception, \ + "only leftmost elements can be none" + else: + f = i + break + + cleanDate(data, f) + else: + raise Exception, "invalid type" + except Exception, e: + raise ValueError, "invalid %s value - %s" % (self._type, e) + + return tuple(data) + + def _marshalData(self): + if self._cache == None: + d = self._data + e = list(d) + neg = '' + + if e[0] < 0: + neg = '-' + e[0] = abs(e[0]) + + if not e[0]: + e[0] = '--' + elif e[0] < 100: + e[0] = '-' + "%02d" % e[0] + else: + e[0] = "%04d" % e[0] + + for i in range(1, len(e)): + if e[i] == None or (i < 3 and e[i] == 0): + e[i] = '-' + else: + if e[i] < 0: + neg = '-' + e[i] = abs(e[i]) + + e[i] = "%02d" % e[i] + + if d[5]: + f = abs(d[5] - int(d[5])) + + if f: + e[5] += ("%g" % f)[1:] + + s = "%s%s-%s-%sT%s:%s:%sZ" % ((neg,) + tuple(e)) + + self._cache = s + + return self._cache + +class timeInstantType(dateTimeType): + _validURIs = (NS.XSD, NS.XSD2, NS.ENC) + +class timePeriodType(dateTimeType): + _validURIs = (NS.XSD2, NS.ENC) + +class timeType(anyType): + def _checkValueSpace(self, data): + try: + if data == None: + data = time.gmtime(time.time())[3:6] + elif (type(data) == FloatType): + f = data - int(data) + data = list(time.gmtime(int(data))[3:6]) + data[2] += f + elif type(data) in (IntType, LongType): + data = time.gmtime(data)[3:6] + elif type(data) in (ListType, TupleType): + if len(data) == 9: + data = data[3:6] + elif len(data) > 3: + raise Exception, "too many values" + + data = [None, None, None] + list(data) + + if len(data) < 6: + data += [0] * (6 - len(data)) + + cleanDate(data, 3) + + data = data[3:] + else: + raise Exception, "invalid type" + except Exception, e: + raise ValueError, "invalid %s value - %s" % (self._type, e) + + return tuple(data) + + def _marshalData(self): + if self._cache == None: + d = self._data + s = '' + + s = time.strftime("%H:%M:%S", (0, 0, 0) + d + (0, 0, -1)) + f = d[2] - int(d[2]) + if f != 0: + s += ("%g" % f)[1:] + s += 'Z' + + self._cache = s + + return self._cache + +class dateType(anyType): + def _checkValueSpace(self, data): + try: + if data == None: + data = time.gmtime(time.time())[0:3] + elif type(data) in (IntType, LongType, FloatType): + data = time.gmtime(data)[0:3] + elif type(data) in (ListType, TupleType): + if len(data) == 9: + data = data[0:3] + elif len(data) > 3: + raise Exception, "too many values" + + data = list(data) + + if len(data) < 3: + data += [1, 1, 1][len(data):] + + data += [0, 0, 0] + + cleanDate(data) + + data = data[:3] + else: + raise Exception, "invalid type" + except Exception, e: + raise ValueError, "invalid %s value - %s" % (self._type, e) + + return tuple(data) + + def _marshalData(self): + if self._cache == None: + d = self._data + s = "%04d-%02d-%02dZ" % ((abs(d[0]),) + d[1:]) + if d[0] < 0: + s = '-' + s + + self._cache = s + + return self._cache + +class gYearMonthType(anyType): + _validURIs = (NS.XSD3,) + + def _checkValueSpace(self, data): + try: + if data == None: + data = time.gmtime(time.time())[0:2] + elif type(data) in (IntType, LongType, FloatType): + data = time.gmtime(data)[0:2] + elif type(data) in (ListType, TupleType): + if len(data) == 9: + data = data[0:2] + elif len(data) > 2: + raise Exception, "too many values" + + data = list(data) + + if len(data) < 2: + data += [1, 1][len(data):] + + data += [1, 0, 0, 0] + + cleanDate(data) + + data = data[:2] + else: + raise Exception, "invalid type" + except Exception, e: + raise ValueError, "invalid %s value - %s" % (self._type, e) + + return tuple(data) + + def _marshalData(self): + if self._cache == None: + d = self._data + s = "%04d-%02dZ" % ((abs(d[0]),) + d[1:]) + if d[0] < 0: + s = '-' + s + + self._cache = s + + return self._cache + +class gYearType(anyType): + _validURIs = (NS.XSD3,) + + def _checkValueSpace(self, data): + try: + if data == None: + data = time.gmtime(time.time())[0:1] + elif type(data) in (IntType, LongType, FloatType): + data = [data] + + if type(data) in (ListType, TupleType): + if len(data) == 9: + data = data[0:1] + elif len(data) < 1: + raise Exception, "too few values" + elif len(data) > 1: + raise Exception, "too many values" + + if type(data[0]) == FloatType: + try: s = int(data[0]) + except: s = long(data[0]) + + if s != data[0]: + raise Exception, "not integral" + + data = [s] + elif type(data[0]) not in (IntType, LongType): + raise Exception, "bad type" + else: + raise Exception, "invalid type" + except Exception, e: + raise ValueError, "invalid %s value - %s" % (self._type, e) + + return data[0] + + def _marshalData(self): + if self._cache == None: + d = self._data + s = "%04dZ" % abs(d) + if d < 0: + s = '-' + s + + self._cache = s + + return self._cache + +class centuryType(anyType): + _validURIs = (NS.XSD2, NS.ENC) + + def _checkValueSpace(self, data): + try: + if data == None: + data = time.gmtime(time.time())[0:1] / 100 + elif type(data) in (IntType, LongType, FloatType): + data = [data] + + if type(data) in (ListType, TupleType): + if len(data) == 9: + data = data[0:1] / 100 + elif len(data) < 1: + raise Exception, "too few values" + elif len(data) > 1: + raise Exception, "too many values" + + if type(data[0]) == FloatType: + try: s = int(data[0]) + except: s = long(data[0]) + + if s != data[0]: + raise Exception, "not integral" + + data = [s] + elif type(data[0]) not in (IntType, LongType): + raise Exception, "bad type" + else: + raise Exception, "invalid type" + except Exception, e: + raise ValueError, "invalid %s value - %s" % (self._type, e) + + return data[0] + + def _marshalData(self): + if self._cache == None: + d = self._data + s = "%02dZ" % abs(d) + if d < 0: + s = '-' + s + + self._cache = s + + return self._cache + +class yearType(gYearType): + _validURIs = (NS.XSD2, NS.ENC) + +class gMonthDayType(anyType): + _validURIs = (NS.XSD3,) + + def _checkValueSpace(self, data): + try: + if data == None: + data = time.gmtime(time.time())[1:3] + elif type(data) in (IntType, LongType, FloatType): + data = time.gmtime(data)[1:3] + elif type(data) in (ListType, TupleType): + if len(data) == 9: + data = data[0:2] + elif len(data) > 2: + raise Exception, "too many values" + + data = list(data) + + if len(data) < 2: + data += [1, 1][len(data):] + + data = [0] + data + [0, 0, 0] + + cleanDate(data, 1) + + data = data[1:3] + else: + raise Exception, "invalid type" + except Exception, e: + raise ValueError, "invalid %s value - %s" % (self._type, e) + + return tuple(data) + + def _marshalData(self): + if self._cache == None: + self._cache = "--%02d-%02dZ" % self._data + + return self._cache + +class recurringDateType(gMonthDayType): + _validURIs = (NS.XSD2, NS.ENC) + +class gMonthType(anyType): + _validURIs = (NS.XSD3,) + + def _checkValueSpace(self, data): + try: + if data == None: + data = time.gmtime(time.time())[1:2] + elif type(data) in (IntType, LongType, FloatType): + data = [data] + + if type(data) in (ListType, TupleType): + if len(data) == 9: + data = data[1:2] + elif len(data) < 1: + raise Exception, "too few values" + elif len(data) > 1: + raise Exception, "too many values" + + if type(data[0]) == FloatType: + try: s = int(data[0]) + except: s = long(data[0]) + + if s != data[0]: + raise Exception, "not integral" + + data = [s] + elif type(data[0]) not in (IntType, LongType): + raise Exception, "bad type" + + if data[0] < 1 or data[0] > 12: + raise Exception, "bad value" + else: + raise Exception, "invalid type" + except Exception, e: + raise ValueError, "invalid %s value - %s" % (self._type, e) + + return data[0] + + def _marshalData(self): + if self._cache == None: + self._cache = "--%02d--Z" % self._data + + return self._cache + +class monthType(gMonthType): + _validURIs = (NS.XSD2, NS.ENC) + +class gDayType(anyType): + _validURIs = (NS.XSD3,) + + def _checkValueSpace(self, data): + try: + if data == None: + data = time.gmtime(time.time())[2:3] + elif type(data) in (IntType, LongType, FloatType): + data = [data] + + if type(data) in (ListType, TupleType): + if len(data) == 9: + data = data[2:3] + elif len(data) < 1: + raise Exception, "too few values" + elif len(data) > 1: + raise Exception, "too many values" + + if type(data[0]) == FloatType: + try: s = int(data[0]) + except: s = long(data[0]) + + if s != data[0]: + raise Exception, "not integral" + + data = [s] + elif type(data[0]) not in (IntType, LongType): + raise Exception, "bad type" + + if data[0] < 1 or data[0] > 31: + raise Exception, "bad value" + else: + raise Exception, "invalid type" + except Exception, e: + raise ValueError, "invalid %s value - %s" % (self._type, e) + + return data[0] + + def _marshalData(self): + if self._cache == None: + self._cache = "---%02dZ" % self._data + + return self._cache + +class recurringDayType(gDayType): + _validURIs = (NS.XSD2, NS.ENC) + +class hexBinaryType(anyType): + _validURIs = (NS.XSD3,) + + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + if type(data) not in (StringType, UnicodeType): + raise AttributeError, "invalid %s type" % self._type + + return data + + def _marshalData(self): + if self._cache == None: + self._cache = encodeHexString(self._data) + + return self._cache + +class base64BinaryType(anyType): + _validURIs = (NS.XSD3,) + + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + if type(data) not in (StringType, UnicodeType): + raise AttributeError, "invalid %s type" % self._type + + return data + + def _marshalData(self): + if self._cache == None: + self._cache = base64.encodestring(self._data) + + return self._cache + +class base64Type(base64BinaryType): + _validURIs = (NS.ENC,) + +class binaryType(anyType): + _validURIs = (NS.XSD, NS.ENC) + + def __init__(self, data, name = None, typed = 1, encoding = 'base64', + attrs = None): + + anyType.__init__(self, data, name, typed, attrs) + + self._setAttr('encoding', encoding) + + def _marshalData(self): + if self._cache == None: + if self._getAttr((None, 'encoding')) == 'base64': + self._cache = base64.encodestring(self._data) + else: + self._cache = encodeHexString(self._data) + + return self._cache + + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + if type(data) not in (StringType, UnicodeType): + raise AttributeError, "invalid %s type" % self._type + + return data + + def _setAttr(self, attr, value): + attr = self._fixAttr(attr) + + if attr[1] == 'encoding': + if attr[0] != None or value not in ('base64', 'hex'): + raise AttributeError, "invalid encoding" + + self._cache = None + + anyType._setAttr(self, attr, value) + + +class anyURIType(anyType): + _validURIs = (NS.XSD3,) + + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + if type(data) not in (StringType, UnicodeType): + raise AttributeError, "invalid %s type" % self._type + + return data + + def _marshalData(self): + if self._cache == None: + self._cache = urllib.quote(self._data) + + return self._cache + +class uriType(anyURIType): + _validURIs = (NS.XSD,) + +class uriReferenceType(anyURIType): + _validURIs = (NS.XSD2,) + +class NOTATIONType(anyType): + def __init__(self, data, name = None, typed = 1, attrs = None): + + if self.__class__ == NOTATIONType: + raise Error, "a NOTATION can't be instantiated directly" + + anyType.__init__(self, data, name, typed, attrs) + +class ENTITIESType(anyType): + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + if type(data) in (StringType, UnicodeType): + return (data,) + + if type(data) not in (ListType, TupleType) or \ + filter (lambda x: type(x) not in (StringType, UnicodeType), data): + raise AttributeError, "invalid %s type" % self._type + + return data + + def _marshalData(self): + return ' '.join(self._data) + +class IDREFSType(ENTITIESType): pass +class NMTOKENSType(ENTITIESType): pass + +class integerType(anyType): + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + if type(data) not in (IntType, LongType): + raise ValueError, "invalid %s value" % self._type + + return data + +class nonPositiveIntegerType(anyType): + _validURIs = (NS.XSD2, NS.XSD3, NS.ENC) + + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + if type(data) not in (IntType, LongType) or data > 0: + raise ValueError, "invalid %s value" % self._type + + return data + +class non_Positive_IntegerType(nonPositiveIntegerType): + _validURIs = (NS.XSD,) + + def _typeName(self): + return 'non-positive-integer' + +class negativeIntegerType(anyType): + _validURIs = (NS.XSD2, NS.XSD3, NS.ENC) + + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + if type(data) not in (IntType, LongType) or data >= 0: + raise ValueError, "invalid %s value" % self._type + + return data + +class negative_IntegerType(negativeIntegerType): + _validURIs = (NS.XSD,) + + def _typeName(self): + return 'negative-integer' + +class longType(anyType): + _validURIs = (NS.XSD2, NS.XSD3, NS.ENC) + + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + if type(data) not in (IntType, LongType) or \ + data < -9223372036854775808L or \ + data > 9223372036854775807L: + raise ValueError, "invalid %s value" % self._type + + return data + +class intType(anyType): + _validURIs = (NS.XSD2, NS.XSD3, NS.ENC) + + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + if type(data) not in (IntType, LongType) or \ + data < -2147483648L or \ + data > 2147483647: + raise ValueError, "invalid %s value" % self._type + + return data + +class shortType(anyType): + _validURIs = (NS.XSD2, NS.XSD3, NS.ENC) + + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + if type(data) not in (IntType, LongType) or \ + data < -32768 or \ + data > 32767: + raise ValueError, "invalid %s value" % self._type + + return data + +class byteType(anyType): + _validURIs = (NS.XSD2, NS.XSD3, NS.ENC) + + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + if type(data) not in (IntType, LongType) or \ + data < -128 or \ + data > 127: + raise ValueError, "invalid %s value" % self._type + + return data + +class nonNegativeIntegerType(anyType): + _validURIs = (NS.XSD2, NS.XSD3, NS.ENC) + + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + if type(data) not in (IntType, LongType) or data < 0: + raise ValueError, "invalid %s value" % self._type + + return data + +class non_Negative_IntegerType(nonNegativeIntegerType): + _validURIs = (NS.XSD,) + + def _typeName(self): + return 'non-negative-integer' + +class unsignedLongType(anyType): + _validURIs = (NS.XSD2, NS.XSD3, NS.ENC) + + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + if type(data) not in (IntType, LongType) or \ + data < 0 or \ + data > 18446744073709551615L: + raise ValueError, "invalid %s value" % self._type + + return data + +class unsignedIntType(anyType): + _validURIs = (NS.XSD2, NS.XSD3, NS.ENC) + + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + if type(data) not in (IntType, LongType) or \ + data < 0 or \ + data > 4294967295L: + raise ValueError, "invalid %s value" % self._type + + return data + +class unsignedShortType(anyType): + _validURIs = (NS.XSD2, NS.XSD3, NS.ENC) + + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + if type(data) not in (IntType, LongType) or \ + data < 0 or \ + data > 65535: + raise ValueError, "invalid %s value" % self._type + + return data + +class unsignedByteType(anyType): + _validURIs = (NS.XSD2, NS.XSD3, NS.ENC) + + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + if type(data) not in (IntType, LongType) or \ + data < 0 or \ + data > 255: + raise ValueError, "invalid %s value" % self._type + + return data + +class positiveIntegerType(anyType): + _validURIs = (NS.XSD2, NS.XSD3, NS.ENC) + + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + if type(data) not in (IntType, LongType) or data <= 0: + raise ValueError, "invalid %s value" % self._type + + return data + +class positive_IntegerType(positiveIntegerType): + _validURIs = (NS.XSD,) + + def _typeName(self): + return 'positive-integer' + +# Now compound types + +class compoundType(anyType): + def __init__(self, data = None, name = None, typed = 1, attrs = None): + if self.__class__ == compoundType: + raise Error, "a compound can't be instantiated directly" + + anyType.__init__(self, data, name, typed, attrs) + self._aslist = [] + self._asdict = {} + self._keyord = [] + + if type(data) == DictType: + self.__dict__.update(data) + + def __getitem__(self, item): + if type(item) == IntType: + return self._aslist[item] + return getattr(self, item) + + def __len__(self): + return len(self._aslist) + + def __nonzero__(self): + return 1 + + def _keys(self): + return filter(lambda x: x[0] != '_', self.__dict__.keys()) + + def _addItem(self, name, value, attrs = None): + d = self._asdict + + if d.has_key(name): + if type(d[name]) != ListType: + d[name] = [d[name]] + d[name].append(value) + else: + d[name] = value + + self._keyord.append(name) + self._aslist.append(value) + self.__dict__[name] = d[name] + + def _placeItem(self, name, value, pos, subpos = 0, attrs = None): + d = self._asdict + + if subpos == 0 and type(d[name]) != ListType: + d[name] = value + else: + d[name][subpos] = value + + self._keyord[pos] = name + self._aslist[pos] = value + self.__dict__[name] = d[name] + + def _getItemAsList(self, name, default = []): + try: + d = self.__dict__[name] + except: + return default + + if type(d) == ListType: + return d + return [d] + +class structType(compoundType): + pass + +class headerType(structType): + _validURIs = (NS.ENV,) + + def __init__(self, data = None, typed = 1, attrs = None): + structType.__init__(self, data, "Header", typed, attrs) + +class bodyType(structType): + _validURIs = (NS.ENV,) + + def __init__(self, data = None, typed = 1, attrs = None): + structType.__init__(self, data, "Body", typed, attrs) + +class arrayType(UserList.UserList, compoundType): + def __init__(self, data = None, name = None, attrs = None, + offset = 0, rank = None, asize = 0, elemsname = None): + + if data: + if type(data) not in (ListType, TupleType): + raise Error, "Data must be a sequence" + + UserList.UserList.__init__(self, data) + compoundType.__init__(self, data, name, 0, attrs) + + self._elemsname = elemsname or "item" + + if data == None: + self._rank = rank + + # According to 5.4.2.2 in the SOAP spec, each element in a + # sparse array must have a position. _posstate keeps track of + # whether we've seen a position or not. It's possible values + # are: + # -1 No elements have been added, so the state is indeterminate + # 0 An element without a position has been added, so no + # elements can have positions + # 1 An element with a position has been added, so all elements + # must have positions + + self._posstate = -1 + + self._full = 0 + + if asize in ('', None): + asize = '0' + + self._dims = map (lambda x: int(x), str(asize).split(',')) + self._dims.reverse() # It's easier to work with this way + self._poss = [0] * len(self._dims) # This will end up + # reversed too + + for i in range(len(self._dims)): + if self._dims[i] < 0 or \ + self._dims[i] == 0 and len(self._dims) > 1: + raise TypeError, "invalid Array dimensions" + + if offset > 0: + self._poss[i] = offset % self._dims[i] + offset = int(offset / self._dims[i]) + + # Don't break out of the loop if offset is 0 so we test all the + # dimensions for > 0. + if offset: + raise AttributeError, "invalid Array offset" + + a = [None] * self._dims[0] + + for i in range(1, len(self._dims)): + b = [] + + for j in range(self._dims[i]): + b.append(copy.deepcopy(a)) + + a = b + + self.data = a + + def _addItem(self, name, value, attrs): + if self._full: + raise ValueError, "Array is full" + + pos = attrs.get((NS.ENC, 'position')) + + if pos != None: + if self._posstate == 0: + raise AttributeError, \ + "all elements in a sparse Array must have a " \ + "position attribute" + + self._posstate = 1 + + try: + if pos[0] == '[' and pos[-1] == ']': + pos = map (lambda x: int(x), pos[1:-1].split(',')) + pos.reverse() + + if len(pos) == 1: + pos = pos[0] + + curpos = [0] * len(self._dims) + + for i in range(len(self._dims)): + curpos[i] = pos % self._dims[i] + pos = int(pos / self._dims[i]) + + if pos == 0: + break + + if pos: + raise Exception + elif len(pos) != len(self._dims): + raise Exception + else: + for i in range(len(self._dims)): + if pos[i] >= self._dims[i]: + raise Exception + + curpos = pos + else: + raise Exception + except: + raise AttributeError, \ + "invalid Array element position %s" % str(pos) + else: + if self._posstate == 1: + raise AttributeError, \ + "only elements in a sparse Array may have a " \ + "position attribute" + + self._posstate = 0 + + curpos = self._poss + + a = self.data + + for i in range(len(self._dims) - 1, 0, -1): + a = a[curpos[i]] + + if curpos[0] >= len(a): + a += [None] * (len(a) - curpos[0] + 1) + + a[curpos[0]] = value + + if pos == None: + self._poss[0] += 1 + + for i in range(len(self._dims) - 1): + if self._poss[i] < self._dims[i]: + break + + self._poss[i] = 0 + self._poss[i + 1] += 1 + + if self._dims[-1] and self._poss[-1] >= self._dims[-1]: + self._full = 1 + + def _placeItem(self, name, value, pos, subpos, attrs = None): + curpos = [0] * len(self._dims) + + for i in range(len(self._dims)): + if self._dims[i] == 0: + curpos[0] = pos + break + + curpos[i] = pos % self._dims[i] + pos = int(pos / self._dims[i]) + + if pos == 0: + break + + if self._dims[i] != 0 and pos: + raise Error, "array index out of range" + + a = self.data + + for i in range(len(self._dims) - 1, 0, -1): + a = a[curpos[i]] + + if curpos[0] >= len(a): + a += [None] * (len(a) - curpos[0] + 1) + + a[curpos[0]] = value + +class typedArrayType(arrayType): + def __init__(self, data = None, name = None, typed = None, attrs = None, + offset = 0, rank = None, asize = 0, elemsname = None): + + arrayType.__init__(self, data, name, attrs, offset, rank, asize, + elemsname) + + self._type = typed + +class faultType(structType, Error): + def __init__(self, faultcode = "", faultstring = "", detail = None): + self.faultcode = faultcode + self.faultstring = faultstring + if detail != None: + self.detail = detail + + structType.__init__(self, None, 0) + + def _setDetail(self, detail = None): + if detail != None: + self.detail = detail + else: + try: del self.detail + except AttributeError: pass + + def __repr__(self): + return "" % (self.faultcode, self.faultstring) + + __str__ = __repr__ + +################################################################################ +class RefHolder: + def __init__(self, name, frame): + self.name = name + self.parent = frame + self.pos = len(frame) + self.subpos = frame.namecounts.get(name, 0) + + def __repr__(self): + return "<%s %s at %d>" % (self.__class__, self.name, id(self)) + +################################################################################ +# Utility infielders +################################################################################ +def collapseWhiteSpace(s): + return re.sub('\s+', ' ', s).strip() + +def decodeHexString(data): + conv = {'0': 0x0, '1': 0x1, '2': 0x2, '3': 0x3, '4': 0x4, + '5': 0x5, '6': 0x6, '7': 0x7, '8': 0x8, '9': 0x9, 'a': 0xa, + 'b': 0xb, 'c': 0xc, 'd': 0xd, 'e': 0xe, 'f': 0xf, 'A': 0xa, + 'B': 0xb, 'C': 0xc, 'D': 0xd, 'E': 0xe, 'F': 0xf,} + ws = string.whitespace + + bin = '' + + i = 0 + + while i < len(data): + if data[i] not in ws: + break + i += 1 + + low = 0 + + while i < len(data): + c = data[i] + + if c in string.whitespace: + break + + try: + c = conv[c] + except KeyError: + raise ValueError, \ + "invalid hex string character `%s'" % c + + if low: + bin += chr(high * 16 + c) + low = 0 + else: + high = c + low = 1 + + i += 1 + + if low: + raise ValueError, "invalid hex string length" + + while i < len(data): + if data[i] not in string.whitespace: + raise ValueError, \ + "invalid hex string character `%s'" % c + + i += 1 + + return bin + +def encodeHexString(data): + h = '' + + for i in data: + h += "%02X" % ord(i) + + return h + +def leapMonth(year, month): + return month == 2 and \ + year % 4 == 0 and \ + (year % 100 != 0 or year % 400 == 0) + +def cleanDate(d, first = 0): + ranges = (None, (1, 12), (1, 31), (0, 23), (0, 59), (0, 61)) + months = (0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31) + names = ('year', 'month', 'day', 'hours', 'minutes', 'seconds') + + if len(d) != 6: + raise ValueError, "date must have 6 elements" + + for i in range(first, 6): + s = d[i] + + if type(s) == FloatType: + if i < 5: + try: + s = int(s) + except OverflowError: + if i > 0: + raise + s = long(s) + + if s != d[i]: + raise ValueError, "%s must be integral" % names[i] + + d[i] = s + elif type(s) == LongType: + try: s = int(s) + except: pass + elif type(s) != IntType: + raise TypeError, "%s isn't a valid type" % names[i] + + if i == first and s < 0: + continue + + if ranges[i] != None and \ + (s < ranges[i][0] or ranges[i][1] < s): + raise ValueError, "%s out of range" % names[i] + + if first < 6 and d[5] >= 61: + raise ValueError, "seconds out of range" + + if first < 2: + leap = first < 1 and leapMonth(d[0], d[1]) + + if d[2] > months[d[1]] + leap: + raise ValueError, "day out of range" + +class UnderflowError(exceptions.ArithmeticError): + pass + +def debugHeader(title): + s = '*** ' + title + ' ' + print s + ('*' * (72 - len(s))) + +def debugFooter(title): + print '*' * 72 + sys.stdout.flush() + +################################################################################ +# SOAP Parser +################################################################################ +class SOAPParser(xml.sax.handler.ContentHandler): + class Frame: + def __init__(self, name, kind = None, attrs = {}, rules = {}): + self.name = name + self.kind = kind + self.attrs = attrs + self.rules = rules + + self.contents = [] + self.names = [] + self.namecounts = {} + self.subattrs = [] + + def append(self, name, data, attrs): + self.names.append(name) + self.contents.append(data) + self.subattrs.append(attrs) + + if self.namecounts.has_key(name): + self.namecounts[name] += 1 + else: + self.namecounts[name] = 1 + + def _placeItem(self, name, value, pos, subpos = 0, attrs = None): + self.contents[pos] = value + + if attrs: + self.attrs.update(attrs) + + def __len__(self): + return len(self.contents) + + def __repr__(self): + return "<%s %s at %d>" % (self.__class__, self.name, id(self)) + + def __init__(self, rules = None): + xml.sax.handler.ContentHandler.__init__(self) + self.body = None + self.header = None + self.attrs = {} + self._data = None + self._next = "E" # Keeping state for message validity + self._stack = [self.Frame('SOAP')] + + # Make two dictionaries to store the prefix <-> URI mappings, and + # initialize them with the default + self._prem = {NS.XML_T: NS.XML} + self._prem_r = {NS.XML: NS.XML_T} + self._ids = {} + self._refs = {} + self._rules = rules + + def startElementNS(self, name, qname, attrs): + # Workaround two sax bugs + if name[0] == None and name[1][0] == ' ': + name = (None, name[1][1:]) + else: + name = tuple(name) + + # First some checking of the layout of the message + + if self._next == "E": + if name[1] != 'Envelope': + raise Error, "expected `SOAP-ENV:Envelope', got `%s:%s'" % \ + (self._prem_r[name[0]], name[1]) + if name[0] != NS.ENV: + raise faultType, ("%s:VersionMismatch" % NS.ENV_T, + "Don't understand version `%s' Envelope" % name[0]) + else: + self._next = "HorB" + elif self._next == "HorB": + if name[0] == NS.ENV and name[1] in ("Header", "Body"): + self._next = None + else: + raise Error, \ + "expected `SOAP-ENV:Header' or `SOAP-ENV:Body', " \ + "got `%s'" % self._prem_r[name[0]] + ':' + name[1] + elif self._next == "B": + if name == (NS.ENV, "Body"): + self._next = None + else: + raise Error, "expected `SOAP-ENV:Body', got `%s'" % \ + self._prem_r[name[0]] + ':' + name[1] + elif self._next == "": + raise Error, "expected nothing, got `%s'" % \ + self._prem_r[name[0]] + ':' + name[1] + + if len(self._stack) == 2: + rules = self._rules + else: + try: + rules = self._stack[-1].rules[name[1]] + except: + rules = None + + if type(rules) not in (NoneType, DictType): + kind = rules + else: + kind = attrs.get((NS.ENC, 'arrayType')) + + if kind != None: + del attrs._attrs[(NS.ENC, 'arrayType')] + + i = kind.find(':') + if i >= 0: + kind = (self._prem[kind[:i]], kind[i + 1:]) + else: + kind = None + + self.pushFrame(self.Frame(name[1], kind, attrs._attrs, rules)) + + self._data = '' # Start accumulating + + def pushFrame(self, frame): + self._stack.append(frame) + + def popFrame(self): + return self._stack.pop() + + def endElementNS(self, name, qname): + # Workaround two sax bugs + if name[0] == None and name[1][0] == ' ': + ns, name = None, name[1][1:] + else: + ns, name = tuple(name) + + if self._next == "E": + raise Error, "didn't get SOAP-ENV:Envelope" + if self._next in ("HorB", "B"): + raise Error, "didn't get SOAP-ENV:Body" + + cur = self.popFrame() + attrs = cur.attrs + + idval = None + + if attrs.has_key((None, 'id')): + idval = attrs[(None, 'id')] + + if self._ids.has_key(idval): + raise Error, "duplicate id `%s'" % idval + + del attrs[(None, 'id')] + + root = 1 + + if len(self._stack) == 3: + if attrs.has_key((NS.ENC, 'root')): + root = int(attrs[(NS.ENC, 'root')]) + + # Do some preliminary checks. First, if root="0" is present, + # the element must have an id. Next, if root="n" is present, + # n something other than 0 or 1, raise an exception. + + if root == 0: + if idval == None: + raise Error, "non-root element must have an id" + elif root != 1: + raise Error, "SOAP-ENC:root must be `0' or `1'" + + del attrs[(NS.ENC, 'root')] + + while 1: + href = attrs.get((None, 'href')) + if href: + if href[0] != '#': + raise Error, "only do local hrefs right now" + if self._data != None and self._data.strip() != '': + raise Error, "hrefs can't have data" + + href = href[1:] + + if self._ids.has_key(href): + data = self._ids[href] + else: + data = RefHolder(name, self._stack[-1]) + + if self._refs.has_key(href): + self._refs[href].append(data) + else: + self._refs[href] = [data] + + del attrs[(None, 'href')] + + break + + kind = None + + if attrs: + for i in NS.XSI_L: + if attrs.has_key((i, 'type')): + kind = attrs[(i, 'type')] + del attrs[(i, 'type')] + + if kind != None: + i = kind.find(':') + if i >= 0: + kind = (self._prem[kind[:i]], kind[i + 1:]) + else: +# XXX What to do here? (None, kind) is just going to fail in convertType + kind = (None, kind) + + null = 0 + + if attrs: + for i in (NS.XSI, NS.XSI2): + if attrs.has_key((i, 'null')): + null = attrs[(i, 'null')] + del attrs[(i, 'null')] + + if attrs.has_key((NS.XSI3, 'nil')): + null = attrs[(NS.XSI3, 'nil')] + del attrs[(NS.XSI3, 'nil')] + + #MAP 4/12/2002 - must also support "true" + #null = int(null) + null = (str(null).lower() in ['true', '1']) + + if null: + if len(cur) or \ + (self._data != None and self._data.strip() != ''): + raise Error, "nils can't have data" + + data = None + + break + + if len(self._stack) == 2: + if (ns, name) == (NS.ENV, "Header"): + self.header = data = headerType(attrs = attrs) + self._next = "B" + break + elif (ns, name) == (NS.ENV, "Body"): + self.body = data = bodyType(attrs = attrs) + self._next = "" + break + elif len(self._stack) == 3 and self._next == None: + if (ns, name) == (NS.ENV, "Fault"): + data = faultType() + self._next = "" + break + + if cur.rules != None: + rule = cur.rules + + if type(rule) in (StringType, UnicodeType): +# XXX Need a namespace here + rule = (None, rule) + elif type(rule) == ListType: + rule = tuple(rule) + +# XXX What if rule != kind? + if callable(rule): + data = rule(self._data) + elif type(rule) == DictType: + data = structType(name = (ns, name), attrs = attrs) + else: + data = self.convertType(self._data, rule, attrs) + + break + + if (kind == None and cur.kind != None) or \ + (kind == (NS.ENC, 'Array')): + kind = cur.kind + + if kind == None: + kind = 'ur-type[%d]' % len(cur) + else: + kind = kind[1] + + if len(cur.namecounts) == 1: + elemsname = cur.names[0] + else: + elemsname = None + + data = self.startArray((ns, name), kind, attrs, elemsname) + + break + + if len(self._stack) == 3 and kind == None and \ + len(cur) == 0 and \ + (self._data == None or self._data.strip() == ''): + data = structType(name = (ns, name), attrs = attrs) + break + + if len(cur) == 0 and ns != NS.URN: + # Nothing's been added to the current frame so it must be a + # simple type. + + if kind == None: + # If the current item's container is an array, it will + # have a kind. If so, get the bit before the first [, + # which is the type of the array, therefore the type of + # the current item. + + kind = self._stack[-1].kind + + if kind != None: + i = kind[1].find('[') + if i >= 0: + kind = (kind[0], kind[1][:i]) + elif ns != None: + kind = (ns, name) + + if kind != None: + try: + data = self.convertType(self._data, kind, attrs) + except UnknownTypeError: + data = None + else: + data = None + + if data == None: + data = self._data or '' + + if len(attrs) == 0: + try: data = str(data) + except: pass + + break + + data = structType(name = (ns, name), attrs = attrs) + + break + + if isinstance(data, compoundType): + for i in range(len(cur)): + v = cur.contents[i] + data._addItem(cur.names[i], v, cur.subattrs[i]) + + if isinstance(v, RefHolder): + v.parent = data + + if root: + self._stack[-1].append(name, data, attrs) + + if idval != None: + self._ids[idval] = data + + if self._refs.has_key(idval): + for i in self._refs[idval]: + i.parent._placeItem(i.name, data, i.pos, i.subpos, attrs) + + del self._refs[idval] + + self.attrs[id(data)] = attrs + + if isinstance(data, anyType): + data._setAttrs(attrs) + + self._data = None # Stop accumulating + + def endDocument(self): + if len(self._refs) == 1: + raise Error, \ + "unresolved reference " + self._refs.keys()[0] + elif len(self._refs) > 1: + raise Error, \ + "unresolved references " + ', '.join(self._refs.keys()) + + def startPrefixMapping(self, prefix, uri): + self._prem[prefix] = uri + self._prem_r[uri] = prefix + + def endPrefixMapping(self, prefix): + try: + del self._prem_r[self._prem[prefix]] + del self._prem[prefix] + except: + pass + + def characters(self, c): + if self._data != None: + self._data += c + + arrayre = '^(?:(?P[^:]*):)?' \ + '(?P[^[]+)' \ + '(?:\[(?P,*)\])?' \ + '(?:\[(?P\d+(?:,\d+)*)?\])$' + + def startArray(self, name, kind, attrs, elemsname): + if type(self.arrayre) == StringType: + self.arrayre = re.compile (self.arrayre) + + offset = attrs.get((NS.ENC, "offset")) + + if offset != None: + del attrs[(NS.ENC, "offset")] + + try: + if offset[0] == '[' and offset[-1] == ']': + offset = int(offset[1:-1]) + if offset < 0: + raise Exception + else: + raise Exception + except: + raise AttributeError, "invalid Array offset" + else: + offset = 0 + + try: + m = self.arrayre.search(kind) + + if m == None: + raise Exception + + t = m.group('type') + + if t == 'ur-type': + return arrayType(None, name, attrs, offset, m.group('rank'), + m.group('asize'), elemsname) + elif m.group('ns') != None: + return typedArrayType(None, name, + (self._prem[m.group('ns')], t), attrs, offset, + m.group('rank'), m.group('asize'), elemsname) + else: + return typedArrayType(None, name, (None, t), attrs, offset, + m.group('rank'), m.group('asize'), elemsname) + except: + raise AttributeError, "invalid Array type `%s'" % kind + + # Conversion + + class DATETIMECONSTS: + SIGNre = '(?P-?)' + CENTURYre = '(?P\d{2,})' + YEARre = '(?P\d{2})' + MONTHre = '(?P\d{2})' + DAYre = '(?P\d{2})' + HOURre = '(?P\d{2})' + MINUTEre = '(?P\d{2})' + SECONDre = '(?P\d{2}(?:\.\d*)?)' + TIMEZONEre = '(?PZ)|(?P[-+])(?P\d{2}):' \ + '(?P\d{2})' + BOSre = '^\s*' + EOSre = '\s*$' + + __allres = {'sign': SIGNre, 'century': CENTURYre, 'year': YEARre, + 'month': MONTHre, 'day': DAYre, 'hour': HOURre, + 'minute': MINUTEre, 'second': SECONDre, 'timezone': TIMEZONEre, + 'b': BOSre, 'e': EOSre} + + dateTime = '%(b)s%(sign)s%(century)s%(year)s-%(month)s-%(day)sT' \ + '%(hour)s:%(minute)s:%(second)s(%(timezone)s)?%(e)s' % __allres + timeInstant = dateTime + timePeriod = dateTime + time = '%(b)s%(hour)s:%(minute)s:%(second)s(%(timezone)s)?%(e)s' % \ + __allres + date = '%(b)s%(sign)s%(century)s%(year)s-%(month)s-%(day)s' \ + '(%(timezone)s)?%(e)s' % __allres + century = '%(b)s%(sign)s%(century)s(%(timezone)s)?%(e)s' % __allres + gYearMonth = '%(b)s%(sign)s%(century)s%(year)s-%(month)s' \ + '(%(timezone)s)?%(e)s' % __allres + gYear = '%(b)s%(sign)s%(century)s%(year)s(%(timezone)s)?%(e)s' % \ + __allres + year = gYear + gMonthDay = '%(b)s--%(month)s-%(day)s(%(timezone)s)?%(e)s' % __allres + recurringDate = gMonthDay + gDay = '%(b)s---%(day)s(%(timezone)s)?%(e)s' % __allres + recurringDay = gDay + gMonth = '%(b)s--%(month)s--(%(timezone)s)?%(e)s' % __allres + month = gMonth + + recurringInstant = '%(b)s%(sign)s(%(century)s|-)(%(year)s|-)-' \ + '(%(month)s|-)-(%(day)s|-)T' \ + '(%(hour)s|-):(%(minute)s|-):(%(second)s|-)' \ + '(%(timezone)s)?%(e)s' % __allres + + duration = '%(b)s%(sign)sP' \ + '((?P\d+)Y)?' \ + '((?P\d+)M)?' \ + '((?P\d+)D)?' \ + '((?PT)' \ + '((?P\d+)H)?' \ + '((?P\d+)M)?' \ + '((?P\d*(?:\.\d*)?)S)?)?%(e)s' % \ + __allres + + timeDuration = duration + + # The extra 31 on the front is: + # - so the tuple is 1-based + # - so months[month-1] is December's days if month is 1 + + months = (31, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31) + + def convertDateTime(self, value, kind): + def getZoneOffset(d): + zoffs = 0 + + try: + if d['zulu'] == None: + zoffs = 60 * int(d['tzhour']) + int(d['tzminute']) + if d['tzsign'] != '-': + zoffs = -zoffs + except TypeError: + pass + + return zoffs + + def applyZoneOffset(months, zoffs, date, minfield, posday = 1): + if zoffs == 0 and (minfield > 4 or 0 <= date[5] < 60): + return date + + if minfield > 5: date[5] = 0 + if minfield > 4: date[4] = 0 + + if date[5] < 0: + date[4] += int(date[5]) / 60 + date[5] %= 60 + + date[4] += zoffs + + if minfield > 3 or 0 <= date[4] < 60: return date + + date[3] += date[4] / 60 + date[4] %= 60 + + if minfield > 2 or 0 <= date[3] < 24: return date + + date[2] += date[3] / 24 + date[3] %= 24 + + if minfield > 1: + if posday and date[2] <= 0: + date[2] += 31 # zoffs is at most 99:59, so the + # day will never be less than -3 + return date + + while 1: + # The date[1] == 3 (instead of == 2) is because we're + # going back a month, so we need to know if the previous + # month is February, so we test if this month is March. + + leap = minfield == 0 and date[1] == 3 and \ + date[0] % 4 == 0 and \ + (date[0] % 100 != 0 or date[0] % 400 == 0) + + if 0 < date[2] <= months[date[1]] + leap: break + + date[2] += months[date[1] - 1] + leap + + date[1] -= 1 + + if date[1] > 0: break + + date[1] = 12 + + if minfield > 0: break + + date[0] -= 1 + + return date + + try: + exp = getattr(self.DATETIMECONSTS, kind) + except AttributeError: + return None + + if type(exp) == StringType: + exp = re.compile(exp) + setattr (self.DATETIMECONSTS, kind, exp) + + m = exp.search(value) + + try: + if m == None: + raise Exception + + d = m.groupdict() + f = ('century', 'year', 'month', 'day', + 'hour', 'minute', 'second') + fn = len(f) # Index of first non-None value + r = [] + + if kind in ('duration', 'timeDuration'): + if d['sep'] != None and d['hour'] == None and \ + d['minute'] == None and d['second'] == None: + raise Exception + + f = f[1:] + + for i in range(len(f)): + s = d[f[i]] + + if s != None: + if f[i] == 'second': + s = float(s) + else: + try: s = int(s) + except ValueError: s = long(s) + + if i < fn: fn = i + + r.append(s) + + if fn > len(r): # Any non-Nones? + raise Exception + + if d['sign'] == '-': + r[fn] = -r[fn] + + return tuple(r) + + if kind == 'recurringInstant': + for i in range(len(f)): + s = d[f[i]] + + if s == None or s == '-': + if i > fn: + raise Exception + s = None + else: + if i < fn: + fn = i + + if f[i] == 'second': + s = float(s) + else: + try: + s = int(s) + except ValueError: + s = long(s) + + r.append(s) + + s = r.pop(0) + + if fn == 0: + r[0] += s * 100 + else: + fn -= 1 + + if fn < len(r) and d['sign'] == '-': + r[fn] = -r[fn] + + cleanDate(r, fn) + + return tuple(applyZoneOffset(self.DATETIMECONSTS.months, + getZoneOffset(d), r, fn, 0)) + + r = [0, 0, 1, 1, 0, 0, 0] + + for i in range(len(f)): + field = f[i] + + s = d.get(field) + + if s != None: + if field == 'second': + s = float(s) + else: + try: + s = int(s) + except ValueError: + s = long(s) + + if i < fn: + fn = i + + r[i] = s + + if fn > len(r): # Any non-Nones? + raise Exception + + s = r.pop(0) + + if fn == 0: + r[0] += s * 100 + else: + fn -= 1 + + if d.get('sign') == '-': + r[fn] = -r[fn] + + cleanDate(r, fn) + + zoffs = getZoneOffset(d) + + if zoffs: + r = applyZoneOffset(self.DATETIMECONSTS.months, zoffs, r, fn) + + if kind == 'century': + return r[0] / 100 + + s = [] + + for i in range(1, len(f)): + if d.has_key(f[i]): + s.append(r[i - 1]) + + if len(s) == 1: + return s[0] + return tuple(s) + except Exception, e: + raise Error, "invalid %s value `%s' - %s" % (kind, value, e) + + intlimits = \ + { + 'nonPositiveInteger': (0, None, 0), + 'non-positive-integer': (0, None, 0), + 'negativeInteger': (0, None, -1), + 'negative-integer': (0, None, -1), + 'long': (1, -9223372036854775808L, + 9223372036854775807L), + 'int': (0, -2147483648L, 2147483647), + 'short': (0, -32768, 32767), + 'byte': (0, -128, 127), + 'nonNegativeInteger': (0, 0, None), + 'non-negative-integer': (0, 0, None), + 'positiveInteger': (0, 1, None), + 'positive-integer': (0, 1, None), + 'unsignedLong': (1, 0, 18446744073709551615L), + 'unsignedInt': (0, 0, 4294967295L), + 'unsignedShort': (0, 0, 65535), + 'unsignedByte': (0, 0, 255), + } + floatlimits = \ + { + 'float': (7.0064923216240861E-46, -3.4028234663852886E+38, + 3.4028234663852886E+38), + 'double': (2.4703282292062327E-324, -1.7976931348623158E+308, + 1.7976931348623157E+308), + } + zerofloatre = '[1-9]' + + def convertType(self, d, t, attrs): + dnn = d or '' + + if t[0] in NS.EXSD_L: + if t[1] == "integer": + try: + d = int(d) + if len(attrs): + d = long(d) + except: + d = long(d) + return d + if self.intlimits.has_key (t[1]): + l = self.intlimits[t[1]] + try: d = int(d) + except: d = long(d) + + if l[1] != None and d < l[1]: + raise UnderflowError, "%s too small" % d + if l[2] != None and d > l[2]: + raise OverflowError, "%s too large" % d + + if l[0] or len(attrs): + return long(d) + return d + if t[1] == "string": + if len(attrs): + return unicode(dnn) + try: + return str(dnn) + except: + return dnn + if t[1] == "boolean": + d = d.strip().lower() + if d in ('0', 'false'): + return 0 + if d in ('1', 'true'): + return 1 + raise AttributeError, "invalid boolean value" + if self.floatlimits.has_key (t[1]): + l = self.floatlimits[t[1]] + s = d.strip().lower() + try: + d = float(s) + except: + # Some platforms don't implement the float stuff. This + # is close, but NaN won't be > "INF" as required by the + # standard. + + if s in ("nan", "inf"): + return 1e300**2 + if s == "-inf": + return -1e300**2 + + raise + + if str (d) == 'nan': + if s != 'nan': + raise ValueError, "invalid %s" % t[1] + elif str (d) == '-inf': + if s != '-inf': + raise UnderflowError, "%s too small" % t[1] + elif str (d) == 'inf': + if s != 'inf': + raise OverflowError, "%s too large" % t[1] + elif d < 0: + if d < l[1]: + raise UnderflowError, "%s too small" % t[1] + elif d > 0: + if d < l[0] or d > l[2]: + raise OverflowError, "%s too large" % t[1] + elif d == 0: + if type(self.zerofloatre) == StringType: + self.zerofloatre = re.compile(self.zerofloatre) + + if self.zerofloatre.search(s): + raise UnderflowError, "invalid %s" % t[1] + + return d + if t[1] in ("dateTime", "date", "timeInstant", "time"): + return self.convertDateTime(d, t[1]) + if t[1] == "decimal": + return float(d) + if t[1] in ("language", "QName", "NOTATION", "NMTOKEN", "Name", + "NCName", "ID", "IDREF", "ENTITY"): + return collapseWhiteSpace(d) + if t[1] in ("IDREFS", "ENTITIES", "NMTOKENS"): + d = collapseWhiteSpace(d) + return d.split() + if t[0] in NS.XSD_L: + if t[1] in ("base64", "base64Binary"): + return base64.decodestring(d) + if t[1] == "hexBinary": + return decodeHexString(d) + if t[1] == "anyURI": + return urllib.unquote(collapseWhiteSpace(d)) + if t[1] in ("normalizedString", "token"): + return collapseWhiteSpace(d) + if t[0] == NS.ENC: + if t[1] == "base64": + return base64.decodestring(d) + if t[0] == NS.XSD: + if t[1] == "binary": + try: + e = attrs[(None, 'encoding')] + + if e == 'hex': + return decodeHexString(d) + elif e == 'base64': + return base64.decodestring(d) + except: + pass + + raise Error, "unknown or missing binary encoding" + if t[1] == "uri": + return urllib.unquote(collapseWhiteSpace(d)) + if t[1] == "recurringInstant": + return self.convertDateTime(d, t[1]) + if t[0] in (NS.XSD2, NS.ENC): + if t[1] == "uriReference": + return urllib.unquote(collapseWhiteSpace(d)) + if t[1] == "timePeriod": + return self.convertDateTime(d, t[1]) + if t[1] in ("century", "year"): + return self.convertDateTime(d, t[1]) + if t[0] in (NS.XSD, NS.XSD2, NS.ENC): + if t[1] == "timeDuration": + return self.convertDateTime(d, t[1]) + if t[0] == NS.XSD3: + if t[1] == "anyURI": + return urllib.unquote(collapseWhiteSpace(d)) + if t[1] in ("gYearMonth", "gMonthDay"): + return self.convertDateTime(d, t[1]) + if t[1] == "gYear": + return self.convertDateTime(d, t[1]) + if t[1] == "gMonth": + return self.convertDateTime(d, t[1]) + if t[1] == "gDay": + return self.convertDateTime(d, t[1]) + if t[1] == "duration": + return self.convertDateTime(d, t[1]) + if t[0] in (NS.XSD2, NS.XSD3): + if t[1] == "token": + return collapseWhiteSpace(d) + if t[1] == "recurringDate": + return self.convertDateTime(d, t[1]) + if t[1] == "month": + return self.convertDateTime(d, t[1]) + if t[1] == "recurringDay": + return self.convertDateTime(d, t[1]) + if t[0] == NS.XSD2: + if t[1] == "CDATA": + return collapseWhiteSpace(d) + + raise UnknownTypeError, "unknown type `%s'" % (t[0] + ':' + t[1]) + +################################################################################ +# call to SOAPParser that keeps all of the info +################################################################################ +def _parseSOAP(xml_str, rules = None): + try: + from cStringIO import StringIO + except ImportError: + from StringIO import StringIO + + parser = xml.sax.make_parser() + t = SOAPParser(rules = rules) + parser.setContentHandler(t) + e = xml.sax.handler.ErrorHandler() + parser.setErrorHandler(e) + + inpsrc = xml.sax.xmlreader.InputSource() + inpsrc.setByteStream(StringIO(xml_str)) + + # turn on namespace mangeling + parser.setFeature(xml.sax.handler.feature_namespaces,1) + + parser.parse(inpsrc) + + return t + +################################################################################ +# SOAPParser's more public interface +################################################################################ +def parseSOAP(xml_str, attrs = 0): + t = _parseSOAP(xml_str) + + if attrs: + return t.body, t.attrs + return t.body + + +def parseSOAPRPC(xml_str, header = 0, body = 0, attrs = 0, rules = None): + t = _parseSOAP(xml_str, rules = rules) + p = t.body._aslist[0] + + # Empty string, for RPC this translates into a void + if type(p) in (type(''), type(u'')) and p in ('', u''): + name = "Response" + for k in t.body.__dict__.keys(): + if k[0] != "_": + name = k + p = structType(name) + + if header or body or attrs: + ret = (p,) + if header : ret += (t.header,) + if body: ret += (t.body,) + if attrs: ret += (t.attrs,) + return ret + else: + return p + + +################################################################################ +# SOAP Builder +################################################################################ +class SOAPBuilder: + _xml_top = '\n' + _xml_enc_top = '\n' + _env_top = '%(ENV_T)s:Envelope %(ENV_T)s:encodingStyle="%(ENC)s"' % \ + NS.__dict__ + _env_bot = '\n' % NS.__dict__ + + # Namespaces potentially defined in the Envelope tag. + + _env_ns = {NS.ENC: NS.ENC_T, NS.ENV: NS.ENV_T, + NS.XSD: NS.XSD_T, NS.XSD2: NS.XSD2_T, NS.XSD3: NS.XSD3_T, + NS.XSI: NS.XSI_T, NS.XSI2: NS.XSI2_T, NS.XSI3: NS.XSI3_T} + + def __init__(self, args = (), kw = {}, method = None, namespace = None, + header = None, methodattrs = None, envelope = 1, encoding = 'UTF-8', + use_refs = 0, config = Config): + + # Test the encoding, raising an exception if it's not known + if encoding != None: + ''.encode(encoding) + + self.args = args + self.kw = kw + self.envelope = envelope + self.encoding = encoding + self.method = method + self.namespace = namespace + self.header = header + self.methodattrs= methodattrs + self.use_refs = use_refs + self.config = config + self.out = '' + self.tcounter = 0 + self.ncounter = 1 + self.icounter = 1 + self.envns = {} + self.ids = {} + self.depth = 0 + self.multirefs = [] + self.multis = 0 + self.body = not isinstance(args, bodyType) + + def build(self): + ns_map = {} + + # Cache whether typing is on or not + typed = self.config.typed + + if self.header: + # Create a header. + self.dump(self.header, "Header", typed = typed) + self.header = None # Wipe it out so no one is using it. + if self.body: + # Call genns to record that we've used SOAP-ENV. + self.depth += 1 + body_ns = self.genns(ns_map, NS.ENV)[0] + self.out += "<%sBody>\n" % body_ns + + if self.method: + self.depth += 1 + a = '' + if self.methodattrs: + for (k, v) in self.methodattrs.items(): + a += ' %s="%s"' % (k, v) + + if self.namespace: # Use the namespace info handed to us + methodns, n = self.genns(ns_map, self.namespace) + else: + methodns, n = '', '' + + self.out += '<%s%s%s%s%s>\n' % \ + (methodns, self.method, n, a, self.genroot(ns_map)) + + try: + if type(self.args) != TupleType: + args = (self.args,) + else: + args = self.args + + for i in args: + self.dump(i, typed = typed, ns_map = ns_map) + + for (k, v) in self.kw.items(): + self.dump(v, k, typed = typed, ns_map = ns_map) + except RecursionError: + if self.use_refs == 0: + # restart + b = SOAPBuilder(args = self.args, kw = self.kw, + method = self.method, namespace = self.namespace, + header = self.header, methodattrs = self.methodattrs, + envelope = self.envelope, encoding = self.encoding, + use_refs = 1, config = self.config) + return b.build() + raise + + if self.method: + self.out += "\n" % (methodns, self.method) + self.depth -= 1 + + if self.body: + # dump may add to self.multirefs, but the for loop will keep + # going until it has used all of self.multirefs, even those + # entries added while in the loop. + + self.multis = 1 + + for obj, tag in self.multirefs: + self.dump(obj, tag, typed = typed, ns_map = ns_map) + + self.out += "\n" % body_ns + self.depth -= 1 + + if self.envelope: + e = map (lambda ns: 'xmlns:%s="%s"' % (ns[1], ns[0]), + self.envns.items()) + + self.out = '<' + self._env_top + ' '.join([''] + e) + '>\n' + \ + self.out + \ + self._env_bot + + if self.encoding != None: + self.out = self._xml_enc_top % self.encoding + self.out + + return self.out.encode(self.encoding) + + return self._xml_top + self.out + + def gentag(self): + self.tcounter += 1 + return "v%d" % self.tcounter + + def genns(self, ns_map, nsURI): + if nsURI == None: + return ('', '') + + if type(nsURI) == TupleType: # already a tuple + if len(nsURI) == 2: + ns, nsURI = nsURI + else: + ns, nsURI = None, nsURI[0] + else: + ns = None + + if ns_map.has_key(nsURI): + return (ns_map[nsURI] + ':', '') + + if self._env_ns.has_key(nsURI): + ns = self.envns[nsURI] = ns_map[nsURI] = self._env_ns[nsURI] + return (ns + ':', '') + + if not ns: + ns = "ns%d" % self.ncounter + self.ncounter += 1 + ns_map[nsURI] = ns + if self.config.buildWithNamespacePrefix: + return (ns + ':', ' xmlns:%s="%s"' % (ns, nsURI)) + else: + return ('', ' xmlns="%s"' % (nsURI)) + + def genroot(self, ns_map): + if self.depth != 2: + return '' + + ns, n = self.genns(ns_map, NS.ENC) + return ' %sroot="%d"%s' % (ns, not self.multis, n) + + # checkref checks an element to see if it needs to be encoded as a + # multi-reference element or not. If it returns None, the element has + # been handled and the caller can continue with subsequent elements. + # If it returns a string, the string should be included in the opening + # tag of the marshaled element. + + def checkref(self, obj, tag, ns_map): + if self.depth < 2: + return '' + + if not self.ids.has_key(id(obj)): + n = self.ids[id(obj)] = self.icounter + self.icounter = n + 1 + + if self.use_refs == 0: + return '' + + if self.depth == 2: + return ' id="i%d"' % n + + self.multirefs.append((obj, tag)) + else: + if self.use_refs == 0: + raise RecursionError, "Cannot serialize recursive object" + + n = self.ids[id(obj)] + + if self.multis and self.depth == 2: + return ' id="i%d"' % n + + self.out += '<%s href="#i%d"%s/>\n' % (tag, n, self.genroot(ns_map)) + return None + + # dumpers + + def dump(self, obj, tag = None, typed = 1, ns_map = {}): + ns_map = ns_map.copy() + self.depth += 1 + + if type(tag) not in (NoneType, StringType, UnicodeType): + raise KeyError, "tag must be a string or None" + + try: + meth = getattr(self, "dump_" + type(obj).__name__) + meth(obj, tag, typed, ns_map) + except AttributeError: + if type(obj) == LongType: + obj_type = "integer" + else: + obj_type = type(obj).__name__ + + self.out += self.dumper(None, obj_type, obj, tag, typed, + ns_map, self.genroot(ns_map)) + + self.depth -= 1 + + # generic dumper + def dumper(self, nsURI, obj_type, obj, tag, typed = 1, ns_map = {}, + rootattr = '', id = '', + xml = '<%(tag)s%(type)s%(id)s%(attrs)s%(root)s>%(data)s\n'): + + if nsURI == None: + nsURI = self.config.typesNamespaceURI + + tag = tag or self.gentag() + + a = n = t = '' + if typed and obj_type: + ns, n = self.genns(ns_map, nsURI) + ins = self.genns(ns_map, self.config.schemaNamespaceURI)[0] + t = ' %stype="%s%s"%s' % (ins, ns, obj_type, n) + + try: a = obj._marshalAttrs(ns_map, self) + except: pass + + try: data = obj._marshalData() + except: data = obj + + return xml % {"tag": tag, "type": t, "data": data, "root": rootattr, + "id": id, "attrs": a} + + def dump_float(self, obj, tag, typed = 1, ns_map = {}): + # Terrible windows hack + if not good_float: + if obj == float(1e300**2): + obj = "INF" + elif obj == float(-1e300**2): + obj = "-INF" + + obj = str(obj) + if obj in ('inf', '-inf'): + obj = str(obj).upper() + elif obj == 'nan': + obj = 'NaN' + self.out += self.dumper(None, "float", obj, tag, typed, ns_map, + self.genroot(ns_map)) + + def dump_string(self, obj, tag, typed = 0, ns_map = {}): + tag = tag or self.gentag() + + id = self.checkref(obj, tag, ns_map) + if id == None: + return + + try: data = obj._marshalData() + except: data = obj + + self.out += self.dumper(None, "string", cgi.escape(data), tag, + typed, ns_map, self.genroot(ns_map), id) + + dump_unicode = dump_string + dump_str = dump_string # 4/12/2002 - MAP - for Python 2.2 + + def dump_None(self, obj, tag, typed = 0, ns_map = {}): + tag = tag or self.gentag() + ns = self.genns(ns_map, self.config.schemaNamespaceURI)[0] + + self.out += '<%s %snull="1"%s/>\n' % (tag, ns, self.genroot(ns_map)) + + def dump_list(self, obj, tag, typed = 1, ns_map = {}): + if type(obj) == InstanceType: + data = obj.data + else: + data = obj + + tag = tag or self.gentag() + + id = self.checkref(obj, tag, ns_map) + if id == None: + return + + try: + sample = data[0] + empty = 0 + except: + sample = structType() + empty = 1 + + # First scan list to see if all are the same type + same_type = 1 + + if not empty: + for i in data[1:]: + if type(sample) != type(i) or \ + (type(sample) == InstanceType and \ + sample.__class__ != i.__class__): + same_type = 0 + break + + ndecl = '' + if same_type: + if (isinstance(sample, structType)) or \ + type(sample) == DictType: # force to urn struct + + try: + tns = obj._ns or NS.URN + except: + tns = NS.URN + + ns, ndecl = self.genns(ns_map, tns) + + try: + typename = last._typename + except: + typename = "SOAPStruct" + + t = ns + typename + + elif isinstance(sample, anyType): + ns = sample._validNamespaceURI(self.config.typesNamespaceURI, + self.config.strictNamespaces) + if ns: + ns, ndecl = self.genns(ns_map, ns) + t = ns + sample._type + else: + t = 'ur-type' + else: + t = self.genns(ns_map, self.config.typesNamespaceURI)[0] + \ + type(sample).__name__ + else: + t = self.genns(ns_map, self.config.typesNamespaceURI)[0] + \ + "ur-type" + + try: a = obj._marshalAttrs(ns_map, self) + except: a = '' + + ens, edecl = self.genns(ns_map, NS.ENC) + ins, idecl = self.genns(ns_map, self.config.schemaNamespaceURI) + + self.out += \ + '<%s %sarrayType="%s[%d]" %stype="%sArray"%s%s%s%s%s%s>\n' %\ + (tag, ens, t, len(data), ins, ens, ndecl, edecl, idecl, + self.genroot(ns_map), id, a) + + typed = not same_type + + try: elemsname = obj._elemsname + except: elemsname = "item" + + for i in data: + self.dump(i, elemsname, typed, ns_map) + + self.out += '\n' % tag + + dump_tuple = dump_list + + def dump_dictionary(self, obj, tag, typed = 1, ns_map = {}): + tag = tag or self.gentag() + + id = self.checkref(obj, tag, ns_map) + if id == None: + return + + try: a = obj._marshalAttrs(ns_map, self) + except: a = '' + + self.out += '<%s%s%s%s>\n' % \ + (tag, id, a, self.genroot(ns_map)) + + for (k, v) in obj.items(): + if k[0] != "_": + self.dump(v, k, 1, ns_map) + + self.out += '\n' % tag + dump_dict = dump_dictionary # 4/18/2002 - MAP - for Python 2.2 + + def dump_instance(self, obj, tag, typed = 1, ns_map = {}): + if not tag: + # If it has a name use it. + if isinstance(obj, anyType) and obj._name: + tag = obj._name + else: + tag = self.gentag() + + if isinstance(obj, arrayType): # Array + self.dump_list(obj, tag, typed, ns_map) + return + + if isinstance(obj, faultType): # Fault + cns, cdecl = self.genns(ns_map, NS.ENC) + vns, vdecl = self.genns(ns_map, NS.ENV) + self.out += '''<%sFault %sroot="1"%s%s> +%s +%s +''' % (vns, cns, vdecl, cdecl, obj.faultcode, obj.faultstring) + if hasattr(obj, "detail"): + self.dump(obj.detail, "detail", typed, ns_map) + self.out += "\n" % vns + return + + r = self.genroot(ns_map) + + try: a = obj._marshalAttrs(ns_map, self) + except: a = '' + + if isinstance(obj, voidType): # void + self.out += "<%s%s%s>\n" % (tag, a, r, tag) + return + + id = self.checkref(obj, tag, ns_map) + if id == None: + return + + if isinstance(obj, structType): + # Check for namespace + ndecl = '' + ns = obj._validNamespaceURI(self.config.typesNamespaceURI, + self.config.strictNamespaces) + if ns: + ns, ndecl = self.genns(ns_map, ns) + tag = ns + tag + self.out += "<%s%s%s%s%s>\n" % (tag, ndecl, id, a, r) + + # If we have order use it. + order = 1 + + for i in obj._keys(): + if i not in obj._keyord: + order = 0 + break + if order: + for i in range(len(obj._keyord)): + self.dump(obj._aslist[i], obj._keyord[i], 1, ns_map) + else: + # don't have pristine order information, just build it. + for (k, v) in obj.__dict__.items(): + if k[0] != "_": + self.dump(v, k, 1, ns_map) + + if isinstance(obj, bodyType): + self.multis = 1 + + for v, k in self.multirefs: + self.dump(v, k, typed = typed, ns_map = ns_map) + + self.out += '\n' % tag + + elif isinstance(obj, anyType): + t = '' + + if typed: + ns = obj._validNamespaceURI(self.config.typesNamespaceURI, + self.config.strictNamespaces) + if ns: + ons, ondecl = self.genns(ns_map, ns) + ins, indecl = self.genns(ns_map, + self.config.schemaNamespaceURI) + t = ' %stype="%s%s"%s%s' % \ + (ins, ons, obj._type, ondecl, indecl) + + self.out += '<%s%s%s%s%s>%s\n' % \ + (tag, t, id, a, r, obj._marshalData(), tag) + + else: # Some Class + self.out += '<%s%s%s>\n' % (tag, id, r) + + for (k, v) in obj.__dict__.items(): + if k[0] != "_": + self.dump(v, k, 1, ns_map) + + self.out += '\n' % tag + + +################################################################################ +# SOAPBuilder's more public interface +################################################################################ +def buildSOAP(args=(), kw={}, method=None, namespace=None, header=None, + methodattrs=None,envelope=1,encoding='UTF-8',config=Config): + t = SOAPBuilder(args=args,kw=kw, method=method, namespace=namespace, + header=header, methodattrs=methodattrs,envelope=envelope, + encoding=encoding, config=config) + return t.build() + +################################################################################ +# RPC +################################################################################ + +def SOAPUserAgent(): + return "SOAP.py " + __version__ + " (actzero.com)" + +################################################################################ +# Client +################################################################################ +class SOAPAddress: + def __init__(self, url, config = Config): + proto, uri = urllib.splittype(url) + + # apply some defaults + if uri[0:2] != '//': + if proto != None: + uri = proto + ':' + uri + + uri = '//' + uri + proto = 'http' + + host, path = urllib.splithost(uri) + + try: + int(host) + host = 'localhost:' + host + except: + pass + + if not path: + path = '/' + + if proto not in ('http', 'https'): + raise IOError, "unsupported SOAP protocol" + if proto == 'https' and not config.SSLclient: + raise AttributeError, \ + "SSL client not supported by this Python installation" + + self.proto = proto + self.host = host + self.path = path + + def __str__(self): + return "%(proto)s://%(host)s%(path)s" % self.__dict__ + + __repr__ = __str__ + + +class HTTPTransport: + # Need a Timeout someday? + def call(self, addr, data, soapaction = '', encoding = None, + http_proxy = None, config = Config): + + import httplib + + if not isinstance(addr, SOAPAddress): + addr = SOAPAddress(addr, config) + + # Build a request + if http_proxy: + real_addr = http_proxy + real_path = addr.proto + "://" + addr.host + addr.path + else: + real_addr = addr.host + real_path = addr.path + + if addr.proto == 'https': + r = httplib.HTTPS(real_addr) + else: + r = httplib.HTTP(real_addr) + + r.putrequest("POST", real_path) + + r.putheader("Host", addr.host) + r.putheader("User-agent", SOAPUserAgent()) + t = 'text/xml'; + if encoding != None: + t += '; charset="%s"' % encoding + r.putheader("Content-type", t) + r.putheader("Content-length", str(len(data))) + r.putheader("SOAPAction", '"%s"' % soapaction) + + if config.dumpHeadersOut: + s = 'Outgoing HTTP headers' + debugHeader(s) + print "POST %s %s" % (real_path, r._http_vsn_str) + print "Host:", addr.host + print "User-agent: SOAP.py " + __version__ + " (actzero.com)" + print "Content-type:", t + print "Content-length:", len(data) + print 'SOAPAction: "%s"' % soapaction + debugFooter(s) + + r.endheaders() + + if config.dumpSOAPOut: + s = 'Outgoing SOAP' + debugHeader(s) + print data, + if data[-1] != '\n': + print + debugFooter(s) + + # send the payload + r.send(data) + + # read response line + code, msg, headers = r.getreply() + + if config.dumpHeadersIn: + s = 'Incoming HTTP headers' + debugHeader(s) + if headers.headers: + print "HTTP/1.? %d %s" % (code, msg) + print "\n".join(map (lambda x: x.strip(), headers.headers)) + else: + print "HTTP/0.9 %d %s" % (code, msg) + debugFooter(s) + + if config.dumpSOAPIn: + data = r.getfile().read() + + s = 'Incoming SOAP' + debugHeader(s) + print data, + if data[-1] != '\n': + print + debugFooter(s) + + if code not in (200, 500): + raise HTTPError(code, msg) + + if not config.dumpSOAPIn: + data = r.getfile().read() + + # return response payload + return data + +################################################################################ +# SOAP Proxy +################################################################################ +class SOAPProxy: + def __init__(self, proxy, namespace = None, soapaction = '', + header = None, methodattrs = None, transport = HTTPTransport, + encoding = 'UTF-8', throw_faults = 1, unwrap_results = 1, + http_proxy=None, config = Config): + + # Test the encoding, raising an exception if it's not known + if encoding != None: + ''.encode(encoding) + + self.proxy = SOAPAddress(proxy, config) + self.namespace = namespace + self.soapaction = soapaction + self.header = header + self.methodattrs = methodattrs + self.transport = transport() + self.encoding = encoding + self.throw_faults = throw_faults + self.unwrap_results = unwrap_results + self.http_proxy = http_proxy + self.config = config + + + def __call(self, name, args, kw, ns = None, sa = None, hd = None, + ma = None): + + ns = ns or self.namespace + ma = ma or self.methodattrs + + if sa: # Get soapaction + if type(sa) == TupleType: sa = sa[0] + else: + sa = self.soapaction + + if hd: # Get header + if type(hd) == TupleType: + hd = hd[0] + else: + hd = self.header + + hd = hd or self.header + + if ma: # Get methodattrs + if type(ma) == TupleType: ma = ma[0] + else: + ma = self.methodattrs + ma = ma or self.methodattrs + + m = buildSOAP(args = args, kw = kw, method = name, namespace = ns, + header = hd, methodattrs = ma, encoding = self.encoding, + config = self.config) + + r = self.transport.call(self.proxy, m, sa, encoding = self.encoding, + http_proxy = self.http_proxy, + config = self.config) + + p, attrs = parseSOAPRPC(r, attrs = 1) + + try: + throw_struct = self.throw_faults and \ + isinstance (p, faultType) + except: + throw_struct = 0 + + if throw_struct: + raise p + + # Bubble a regular result up, if there is only element in the + # struct, assume that is the result and return it. + # Otherwise it will return the struct with all the elements + # as attributes. + if self.unwrap_results: + try: + count = 0 + for i in p.__dict__.keys(): + if i[0] != "_": # don't move the private stuff + count += 1 + t = getattr(p, i) + if count == 1: p = t # Only one piece of data, bubble it up + except: + pass + + if self.config.returnAllAttrs: + return p, attrs + return p + + def _callWithBody(self, body): + return self.__call(None, body, {}) + + def __getattr__(self, name): # hook to catch method calls + if name == '__del__': + raise AttributeError, name + else: + return self.__Method(self.__call, name, config = self.config) + + # To handle attribute wierdness + class __Method: + # Some magic to bind a SOAP method to an RPC server. + # Supports "nested" methods (e.g. examples.getStateName) -- concept + # borrowed from xmlrpc/soaplib -- www.pythonware.com + # Altered (improved?) to let you inline namespaces on a per call + # basis ala SOAP::LITE -- www.soaplite.com + + def __init__(self, call, name, ns = None, sa = None, hd = None, + ma = None, config = Config): + + self.__call = call + self.__name = name + self.__ns = ns + self.__sa = sa + self.__hd = hd + self.__ma = ma + self.__config = config + if self.__name[0] == "_": + if self.__name in ["__repr__","__str__"]: + self.__call__ = self.__repr__ + else: + self.__call__ = self.__f_call + else: + self.__call__ = self.__r_call + + def __getattr__(self, name): + if name == '__del__': + raise AttributeError, name + if self.__name[0] == "_": + # Don't nest method if it is a directive + return self.__class__(self.__call, name, self.__ns, + self.__sa, self.__hd, self.__ma) + + return self.__class__(self.__call, "%s.%s" % (self.__name, name), + self.__ns, self.__sa, self.__hd, self.__ma) + + def __f_call(self, *args, **kw): + if self.__name == "_ns": self.__ns = args + elif self.__name == "_sa": self.__sa = args + elif self.__name == "_hd": self.__hd = args + elif self.__name == "_ma": self.__ma = args + return self + + def __r_call(self, *args, **kw): + return self.__call(self.__name, args, kw, self.__ns, self.__sa, + self.__hd, self.__ma) + + def __repr__(self): + return "<%s at %d>" % (self.__class__, id(self)) + +################################################################################ +# Server +################################################################################ + +# Method Signature class for adding extra info to registered funcs, right now +# used just to indicate it should be called with keywords, instead of ordered +# params. +class MethodSig: + def __init__(self, func, keywords=0, context=0): + self.func = func + self.keywords = keywords + self.context = context + self.__name__ = func.__name__ + + def __call__(self, *args, **kw): + return apply(self.func,args,kw) + +class SOAPContext: + def __init__(self, header, body, attrs, xmldata, connection, httpheaders, + soapaction): + + self.header = header + self.body = body + self.attrs = attrs + self.xmldata = xmldata + self.connection = connection + self.httpheaders= httpheaders + self.soapaction = soapaction + +# A class to describe how header messages are handled +class HeaderHandler: + # Initially fail out if there are any problems. + def __init__(self, header, attrs): + for i in header.__dict__.keys(): + if i[0] == "_": + continue + + d = getattr(header, i) + + try: + fault = int(attrs[id(d)][(NS.ENV, 'mustUnderstand')]) + except: + fault = 0 + + if fault: + raise faultType, ("%s:MustUnderstand" % NS.ENV_T, + "Don't understand `%s' header element but " + "mustUnderstand attribute is set." % i) + + +################################################################################ +# SOAP Server +################################################################################ +class SOAPServer(SocketServer.TCPServer): + import BaseHTTPServer + + class SOAPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): + def version_string(self): + return '' + \ + 'SOAP.py ' + __version__ + ' (Python ' + \ + sys.version.split()[0] + ')' + + def date_time_string(self): + self.__last_date_time_string = \ + SOAPServer.BaseHTTPServer.BaseHTTPRequestHandler.\ + date_time_string(self) + + return self.__last_date_time_string + + def do_POST(self): + try: + if self.server.config.dumpHeadersIn: + s = 'Incoming HTTP headers' + debugHeader(s) + print self.raw_requestline.strip() + print "\n".join(map (lambda x: x.strip(), + self.headers.headers)) + debugFooter(s) + + data = self.rfile.read(int(self.headers["content-length"])) + + if self.server.config.dumpSOAPIn: + s = 'Incoming SOAP' + debugHeader(s) + print data, + if data[-1] != '\n': + print + debugFooter(s) + + (r, header, body, attrs) = \ + parseSOAPRPC(data, header = 1, body = 1, attrs = 1) + + method = r._name + args = r._aslist + kw = r._asdict + + ns = r._ns + resp = "" + # For fault messages + if ns: + nsmethod = "%s:%s" % (ns, method) + else: + nsmethod = method + + try: + # First look for registered functions + if self.server.funcmap.has_key(ns) and \ + self.server.funcmap[ns].has_key(method): + f = self.server.funcmap[ns][method] + else: # Now look at registered objects + # Check for nested attributes. This works even if + # there are none, because the split will return + # [method] + f = self.server.objmap[ns] + l = method.split(".") + for i in l: + f = getattr(f, i) + except: + resp = buildSOAP(faultType("%s:Client" % NS.ENV_T, + "No method %s found" % nsmethod, + "%s %s" % tuple(sys.exc_info()[0:2])), + encoding = self.server.encoding, + config = self.server.config) + status = 500 + else: + try: + if header: + x = HeaderHandler(header, attrs) + + # If it's wrapped, some special action may be needed + + if isinstance(f, MethodSig): + c = None + + if f.context: # Build context object + c = SOAPContext(header, body, attrs, data, + self.connection, self.headers, + self.headers["soapaction"]) + + if f.keywords: + # This is lame, but have to de-unicode + # keywords + + strkw = {} + + for (k, v) in kw.items(): + strkw[str(k)] = v + if c: + strkw["_SOAPContext"] = c + fr = apply(f, (), strkw) + elif c: + fr = apply(f, args, {'_SOAPContext':c}) + else: + fr = apply(f, args, {}) + else: + fr = apply(f, args, {}) + + if type(fr) == type(self) and \ + isinstance(fr, voidType): + resp = buildSOAP(kw = {'%sResponse' % method: fr}, + encoding = self.server.encoding, + config = self.server.config) + else: + resp = buildSOAP(kw = + {'%sResponse' % method: {'Result': fr}}, + encoding = self.server.encoding, + config = self.server.config) + except Exception, e: + import traceback + info = sys.exc_info() + + if self.server.config.dumpFaultInfo: + s = 'Method %s exception' % nsmethod + debugHeader(s) + traceback.print_exception(info[0], info[1], + info[2]) + debugFooter(s) + + if isinstance(e, faultType): + f = e + else: + f = faultType("%s:Server" % NS.ENV_T, + "Method %s failed." % nsmethod) + + if self.server.config.returnFaultInfo: + f._setDetail("".join(traceback.format_exception( + info[0], info[1], info[2]))) + elif not hasattr(f, 'detail'): + f._setDetail("%s %s" % (info[0], info[1])) + + resp = buildSOAP(f, encoding = self.server.encoding, + config = self.server.config) + status = 500 + else: + status = 200 + except faultType, e: + import traceback + info = sys.exc_info() + + if self.server.config.dumpFaultInfo: + s = 'Received fault exception' + debugHeader(s) + traceback.print_exception(info[0], info[1], + info[2]) + debugFooter(s) + + if self.server.config.returnFaultInfo: + e._setDetail("".join(traceback.format_exception( + info[0], info[1], info[2]))) + elif not hasattr(e, 'detail'): + e._setDetail("%s %s" % (info[0], info[1])) + + resp = buildSOAP(e, encoding = self.server.encoding, + config = self.server.config) + status = 500 + except: + # internal error, report as HTTP server error + if self.server.config.dumpFaultInfo: + import traceback + s = 'Internal exception' + debugHeader(s) + traceback.print_exc () + debugFooter(s) + self.send_response(500) + self.end_headers() + + if self.server.config.dumpHeadersOut and \ + self.request_version != 'HTTP/0.9': + s = 'Outgoing HTTP headers' + debugHeader(s) + if self.responses.has_key(status): + s = ' ' + self.responses[status][0] + else: + s = '' + print "%s %d%s" % (self.protocol_version, 500, s) + print "Server:", self.version_string() + print "Date:", self.__last_date_time_string + debugFooter(s) + else: + # got a valid SOAP response + self.send_response(status) + + t = 'text/xml'; + if self.server.encoding != None: + t += '; charset="%s"' % self.server.encoding + self.send_header("Content-type", t) + self.send_header("Content-length", str(len(resp))) + self.end_headers() + + if self.server.config.dumpHeadersOut and \ + self.request_version != 'HTTP/0.9': + s = 'Outgoing HTTP headers' + debugHeader(s) + if self.responses.has_key(status): + s = ' ' + self.responses[status][0] + else: + s = '' + print "%s %d%s" % (self.protocol_version, status, s) + print "Server:", self.version_string() + print "Date:", self.__last_date_time_string + print "Content-type:", t + print "Content-length:", len(resp) + debugFooter(s) + + if self.server.config.dumpSOAPOut: + s = 'Outgoing SOAP' + debugHeader(s) + print resp, + if resp[-1] != '\n': + print + debugFooter(s) + + self.wfile.write(resp) + self.wfile.flush() + + # We should be able to shut down both a regular and an SSL + # connection, but under Python 2.1, calling shutdown on an + # SSL connections drops the output, so this work-around. + # This should be investigated more someday. + + if self.server.config.SSLserver and \ + isinstance(self.connection, SSL.Connection): + self.connection.set_shutdown(SSL.SSL_SENT_SHUTDOWN | + SSL.SSL_RECEIVED_SHUTDOWN) + else: + self.connection.shutdown(1) + + def log_message(self, format, *args): + if self.server.log: + SOAPServer.BaseHTTPServer.BaseHTTPRequestHandler.\ + log_message (self, format, *args) + + def __init__(self, addr = ('localhost', 8000), + RequestHandler = SOAPRequestHandler, log = 1, encoding = 'UTF-8', + config = Config, namespace = None, ssl_context = None): + + # Test the encoding, raising an exception if it's not known + if encoding != None: + ''.encode(encoding) + + if ssl_context != None and not config.SSLserver: + raise AttributeError, \ + "SSL server not supported by this Python installation" + + self.namespace = namespace + self.objmap = {} + self.funcmap = {} + self.ssl_context = ssl_context + self.encoding = encoding + self.config = config + self.log = log + + self.allow_reuse_address= 1 + + SocketServer.TCPServer.__init__(self, addr, RequestHandler) + + def get_request(self): + sock, addr = SocketServer.TCPServer.get_request(self) + + if self.ssl_context: + sock = SSL.Connection(self.ssl_context, sock) + sock._setup_ssl(addr) + if sock.accept_ssl() != 1: + raise socket.error, "Couldn't accept SSL connection" + + return sock, addr + + def registerObject(self, object, namespace = ''): + if namespace == '': namespace = self.namespace + self.objmap[namespace] = object + + def registerFunction(self, function, namespace = '', funcName = None): + if not funcName : funcName = function.__name__ + if namespace == '': namespace = self.namespace + if self.funcmap.has_key(namespace): + self.funcmap[namespace][funcName] = function + else: + self.funcmap[namespace] = {funcName : function} + + def registerKWObject(self, object, namespace = ''): + if namespace == '': namespace = self.namespace + for i in dir(object.__class__): + if i[0] != "_" and callable(getattr(object, i)): + self.registerKWFunction(getattr(object,i), namespace) + + # convenience - wraps your func for you. + def registerKWFunction(self, function, namespace = '', funcName = None): + self.registerFunction(MethodSig(function,keywords=1), namespace, + funcName) +# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78: diff --git a/others/amazon.py b/others/amazon.py index d21bef9f5..4b82fe82a 100644 --- a/others/amazon.py +++ b/others/amazon.py @@ -1,275 +1,275 @@ -"""Python wrapper for Amazon web APIs - -This module allows you to access Amazon's web APIs, -to do things like search Amazon and get the results programmatically. -Described here: - http://www.amazon.com/webservices - -You need a Amazon-provided license key to use these services. -Follow the link above to get one. These functions will look in -several places (in this order) for the license key: -- the "license_key" argument of each function -- the module-level LICENSE_KEY variable (call setLicense once to set it) -- an environment variable called AMAZON_LICENSE_KEY -- a file called ".amazonkey" in the current directory -- a file called "amazonkey.txt" in the current directory -- a file called ".amazonkey" in your home directory -- a file called "amazonkey.txt" in your home directory -- a file called ".amazonkey" in the same directory as amazon.py -- a file called "amazonkey.txt" in the same directory as amazon.py - -Sample usage: ->>> import amazon ->>> amazon.setLicense('...') # must get your own key! ->>> pythonBooks = amazon.searchByKeyword('Python') ->>> pythonBooks[0].ProductName -u'Learning Python (Help for Programmers)' ->>> pythonBooks[0].URL -... ->>> pythonBooks[0].OurPrice -... - -Other available functions: -- browseBestSellers -- searchByASIN -- searchByUPC -- searchByAuthor -- searchByArtist -- searchByActor -- searchByDirector -- searchByManufacturer -- searchByListMania -- searchSimilar - -Other usage notes: -- Most functions can take product_line as well, see source for possible values -- All functions can take type="lite" to get less detail in results -- All functions can take page=N to get second, third, fourth page of results -- All functions can take license_key="XYZ", instead of setting it globally -- All functions can take http_proxy="http://x/y/z" which overrides your system setting -""" - -__author__ = "Mark Pilgrim (f8dy@diveintomark.org)" -__version__ = "0.4" -__cvsversion__ = "$Revision$"[11:-2] -__date__ = "$Date$"[7:-2] -__copyright__ = "Copyright (c) 2002 Mark Pilgrim" -__license__ = "Python" - -from xml.dom import minidom -import os, sys, getopt, cgi, urllib -try: - import timeoutsocket # http://www.timo-tasi.org/python/timeoutsocket.py - timeoutsocket.setDefaultSocketTimeout(10) -except ImportError: - pass - -LICENSE_KEY = None -HTTP_PROXY = None - -# don't touch the rest of these constants -class AmazonError(Exception): pass -class NoLicenseKey(Exception): pass -_amazonfile1 = ".amazonkey" -_amazonfile2 = "amazonkey.txt" -_licenseLocations = ( - (lambda key: key, 'passed to the function in license_key variable'), - (lambda key: LICENSE_KEY, 'module-level LICENSE_KEY variable (call setLicense to set it)'), - (lambda key: os.environ.get('AMAZON_LICENSE_KEY', None), 'an environment variable called AMAZON_LICENSE_KEY'), - (lambda key: _contentsOf(os.getcwd(), _amazonfile1), '%s in the current directory' % _amazonfile1), - (lambda key: _contentsOf(os.getcwd(), _amazonfile2), '%s in the current directory' % _amazonfile2), - (lambda key: _contentsOf(os.environ.get('HOME', ''), _amazonfile1), '%s in your home directory' % _amazonfile1), - (lambda key: _contentsOf(os.environ.get('HOME', ''), _amazonfile2), '%s in your home directory' % _amazonfile2), - (lambda key: _contentsOf(_getScriptDir(), _amazonfile1), '%s in the amazon.py directory' % _amazonfile1), - (lambda key: _contentsOf(_getScriptDir(), _amazonfile2), '%s in the amazon.py directory' % _amazonfile2) - ) - -## administrative functions -def version(): - print """PyAmazon %(__version__)s -%(__copyright__)s -released %(__date__)s -""" % globals() - -## utility functions -def setLicense(license_key): - """set license key""" - global LICENSE_KEY - LICENSE_KEY = license_key - -def getLicense(license_key = None): - """get license key - - license key can come from any number of locations; - see module docs for search order""" - for get, location in _licenseLocations: - rc = get(license_key) - if rc: return rc - raise NoLicenseKey, 'get a license key at http://www.amazon.com/webservices' - -def setProxy(http_proxy): - """set HTTP proxy""" - global HTTP_PROXY - HTTP_PROXY = http_proxy - -def getProxy(http_proxy = None): - """get HTTP proxy""" - return http_proxy or HTTP_PROXY - -def getProxies(http_proxy = None): - http_proxy = getProxy(http_proxy) - if http_proxy: - proxies = {"http": http_proxy} - else: - proxies = None - return proxies - -def _contentsOf(dirname, filename): - filename = os.path.join(dirname, filename) - if not os.path.exists(filename): return None - fsock = open(filename) - contents = fsock.read() - fsock.close() - return contents - -def _getScriptDir(): - if __name__ == '__main__': - return os.path.abspath(os.path.dirname(sys.argv[0])) - else: - return os.path.abspath(os.path.dirname(sys.modules[__name__].__file__)) - -class Bag: pass - -def unmarshal(element): - rc = Bag() - if isinstance(element, minidom.Element) and (element.tagName == 'Details'): - rc.URL = element.attributes["url"].value - childElements = [e for e in element.childNodes if isinstance(e, minidom.Element)] - if childElements: - for child in childElements: - key = child.tagName - if hasattr(rc, key): - if type(getattr(rc, key)) <> type([]): - setattr(rc, key, [getattr(rc, key)]) - setattr(rc, key, getattr(rc, key) + [unmarshal(child)]) - else: - setattr(rc, key, unmarshal(child)) - else: - rc = "".join([e.data for e in element.childNodes if isinstance(e, minidom.Text)]) - if element.tagName == 'SalesRank': - rc = int(rc.replace(',', '')) - return rc - -def buildURL(search_type, keyword, product_line, type, page, license_key): - url = "http://xml.amazon.com/onca/xml?v=1.0&f=xml&t=webservices-20" - url += "&dev-t=%s" % license_key.strip() - url += "&type=%s" % type - if page: - url += "&page=%s" % page - if product_line: - url += "&mode=%s" % product_line - url += "&%s=%s" % (search_type, urllib.quote(keyword)) -# print url - return url - -## main functions -def search(search_type, keyword, product_line, type="heavy", page=None, - license_key = None, http_proxy = None): - """search Amazon - - You need a license key to call this function; see - http://www.amazon.com/webservices - to get one. Then you can either pass it to - this function every time, or set it globally; see the module docs for details. - - Parameters: - keyword - keyword to search - search_type - in (KeywordSearch, BrowseNodeSearch, AsinSearch, UpcSearch, AuthorSearch, ArtistSearch, ActorSearch, DirectorSearch, ManufacturerSearch, ListManiaSearch, SimilaritySearch) - product_line - type of product to search for. restrictions based on search_type - UpcSearch - in (music, classical) - AuthorSearch - must be "books" - ArtistSearch - in (music, classical) - ActorSearch - in (dvd, vhs, video) - DirectorSearch - in (dvd, vhs, video) - ManufacturerSearch - in (electronics, kitchen, videogames, software, photo, pc-hardware) - http_proxy (optional) - address of HTTP proxy to use for sending and receiving SOAP messages - - Returns: list of Bags, each Bag may contain the following attributes: - Asin - Amazon ID ("ASIN" number) of this item - Authors - list of authors - Availability - "available", etc. - BrowseList - list of related categories - Catalog - catalog type ("Book", etc) - CollectiblePrice - ?, format "$34.95" - ImageUrlLarge - URL of large image of this item - ImageUrlMedium - URL of medium image of this item - ImageUrlSmall - URL of small image of this item - Isbn - ISBN number - ListPrice - list price, format "$34.95" - Lists - list of ListMania lists that include this item - Manufacturer - manufacturer - Media - media ("Paperback", "Audio CD", etc) - NumMedia - number of different media types in which this item is available - OurPrice - Amazon price, format "$24.47" - ProductName - name of this item - ReleaseDate - release date, format "09 April, 1999" - Reviews - reviews (AvgCustomerRating, plus list of CustomerReview with Rating, Summary, Content) - SalesRank - sales rank (integer) - SimilarProducts - list of Product, which is ASIN number - ThirdPartyNewPrice - ?, format "$34.95" - URL - URL of this item - """ - license_key = getLicense(license_key) - url = buildURL(search_type, keyword, product_line, type, page, license_key) - proxies = getProxies(http_proxy) - u = urllib.FancyURLopener(proxies) - usock = u.open(url) - xmldoc = minidom.parse(usock) - usock.close() - data = unmarshal(xmldoc).ProductInfo - if hasattr(data, 'ErrorMsg'): - raise AmazonError, data.ErrorMsg - else: - return data.Details - -def searchByKeyword(keyword, product_line="books", type="heavy", page=1, license_key=None, http_proxy=None): - return search("KeywordSearch", keyword, product_line, type, page, license_key, http_proxy) - -def browseBestSellers(browse_node, product_line="books", type="heavy", page=1, license_key=None, http_proxy=None): - return search("BrowseNodeSearch", browse_node, product_line, type, page, license_key, http_proxy) - -def searchByASIN(ASIN, type="heavy", license_key=None, http_proxy=None): - return search("AsinSearch", ASIN, None, type, None, license_key, http_proxy) - -def searchByUPC(UPC, type="heavy", license_key=None, http_proxy=None): - return search("UpcSearch", UPC, None, type, None, license_key, http_proxy) - -def searchByAuthor(author, type="heavy", page=1, license_key=None, http_proxy=None): - return search("AuthorSearch", author, "books", type, page, license_key, http_proxy) - -def searchByArtist(artist, product_line="music", type="heavy", page=1, license_key=None, http_proxy=None): - if product_line not in ("music", "classical"): - raise AmazonError, "product_line must be in ('music', 'classical')" - return search("ArtistSearch", artist, product_line, type, page, license_key, http_proxy) - -def searchByActor(actor, product_line="dvd", type="heavy", page=1, license_key=None, http_proxy=None): - if product_line not in ("dvd", "vhs", "video"): - raise AmazonError, "product_line must be in ('dvd', 'vhs', 'video')" - return search("ActorSearch", actor, product_line, type, page, license_key, http_proxy) - -def searchByDirector(director, product_line="dvd", type="heavy", page=1, license_key=None, http_proxy=None): - if product_line not in ("dvd", "vhs", "video"): - raise AmazonError, "product_line must be in ('dvd', 'vhs', 'video')" - return search("DirectorSearch", director, product_line, type, page, license_key, http_proxy) - -def searchByManufacturer(manufacturer, product_line="pc-hardware", type="heavy", page=1, license_key=None, http_proxy=None): - if product_line not in ("electronics", "kitchen", "videogames", "software", "photo", "pc-hardware"): - raise AmazonError, "product_line must be in ('electronics', 'kitchen', 'videogames', 'software', 'photo', 'pc-hardware')" - return search("ManufacturerSearch", manufacturer, product_line, type, page, license_key, http_proxy) - -def searchByListMania(listManiaID, type="heavy", page=1, license_key=None, http_proxy=None): - return search("ListManiaSearch", listManiaID, None, type, page, license_key, http_proxy) - -def searchSimilar(ASIN, type="heavy", page=1, license_key=None, http_proxy=None): - return search("SimilaritySearch", ASIN, None, type, page, license_key, http_proxy) -# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78: +"""Python wrapper for Amazon web APIs + +This module allows you to access Amazon's web APIs, +to do things like search Amazon and get the results programmatically. +Described here: + http://www.amazon.com/webservices + +You need a Amazon-provided license key to use these services. +Follow the link above to get one. These functions will look in +several places (in this order) for the license key: +- the "license_key" argument of each function +- the module-level LICENSE_KEY variable (call setLicense once to set it) +- an environment variable called AMAZON_LICENSE_KEY +- a file called ".amazonkey" in the current directory +- a file called "amazonkey.txt" in the current directory +- a file called ".amazonkey" in your home directory +- a file called "amazonkey.txt" in your home directory +- a file called ".amazonkey" in the same directory as amazon.py +- a file called "amazonkey.txt" in the same directory as amazon.py + +Sample usage: +>>> import amazon +>>> amazon.setLicense('...') # must get your own key! +>>> pythonBooks = amazon.searchByKeyword('Python') +>>> pythonBooks[0].ProductName +u'Learning Python (Help for Programmers)' +>>> pythonBooks[0].URL +... +>>> pythonBooks[0].OurPrice +... + +Other available functions: +- browseBestSellers +- searchByASIN +- searchByUPC +- searchByAuthor +- searchByArtist +- searchByActor +- searchByDirector +- searchByManufacturer +- searchByListMania +- searchSimilar + +Other usage notes: +- Most functions can take product_line as well, see source for possible values +- All functions can take type="lite" to get less detail in results +- All functions can take page=N to get second, third, fourth page of results +- All functions can take license_key="XYZ", instead of setting it globally +- All functions can take http_proxy="http://x/y/z" which overrides your system setting +""" + +__author__ = "Mark Pilgrim (f8dy@diveintomark.org)" +__version__ = "0.4" +__cvsversion__ = "$Revision$"[11:-2] +__date__ = "$Date$"[7:-2] +__copyright__ = "Copyright (c) 2002 Mark Pilgrim" +__license__ = "Python" + +from xml.dom import minidom +import os, sys, getopt, cgi, urllib +try: + import timeoutsocket # http://www.timo-tasi.org/python/timeoutsocket.py + timeoutsocket.setDefaultSocketTimeout(10) +except ImportError: + pass + +LICENSE_KEY = None +HTTP_PROXY = None + +# don't touch the rest of these constants +class AmazonError(Exception): pass +class NoLicenseKey(Exception): pass +_amazonfile1 = ".amazonkey" +_amazonfile2 = "amazonkey.txt" +_licenseLocations = ( + (lambda key: key, 'passed to the function in license_key variable'), + (lambda key: LICENSE_KEY, 'module-level LICENSE_KEY variable (call setLicense to set it)'), + (lambda key: os.environ.get('AMAZON_LICENSE_KEY', None), 'an environment variable called AMAZON_LICENSE_KEY'), + (lambda key: _contentsOf(os.getcwd(), _amazonfile1), '%s in the current directory' % _amazonfile1), + (lambda key: _contentsOf(os.getcwd(), _amazonfile2), '%s in the current directory' % _amazonfile2), + (lambda key: _contentsOf(os.environ.get('HOME', ''), _amazonfile1), '%s in your home directory' % _amazonfile1), + (lambda key: _contentsOf(os.environ.get('HOME', ''), _amazonfile2), '%s in your home directory' % _amazonfile2), + (lambda key: _contentsOf(_getScriptDir(), _amazonfile1), '%s in the amazon.py directory' % _amazonfile1), + (lambda key: _contentsOf(_getScriptDir(), _amazonfile2), '%s in the amazon.py directory' % _amazonfile2) + ) + +## administrative functions +def version(): + print """PyAmazon %(__version__)s +%(__copyright__)s +released %(__date__)s +""" % globals() + +## utility functions +def setLicense(license_key): + """set license key""" + global LICENSE_KEY + LICENSE_KEY = license_key + +def getLicense(license_key = None): + """get license key + + license key can come from any number of locations; + see module docs for search order""" + for get, location in _licenseLocations: + rc = get(license_key) + if rc: return rc + raise NoLicenseKey, 'get a license key at http://www.amazon.com/webservices' + +def setProxy(http_proxy): + """set HTTP proxy""" + global HTTP_PROXY + HTTP_PROXY = http_proxy + +def getProxy(http_proxy = None): + """get HTTP proxy""" + return http_proxy or HTTP_PROXY + +def getProxies(http_proxy = None): + http_proxy = getProxy(http_proxy) + if http_proxy: + proxies = {"http": http_proxy} + else: + proxies = None + return proxies + +def _contentsOf(dirname, filename): + filename = os.path.join(dirname, filename) + if not os.path.exists(filename): return None + fsock = open(filename) + contents = fsock.read() + fsock.close() + return contents + +def _getScriptDir(): + if __name__ == '__main__': + return os.path.abspath(os.path.dirname(sys.argv[0])) + else: + return os.path.abspath(os.path.dirname(sys.modules[__name__].__file__)) + +class Bag: pass + +def unmarshal(element): + rc = Bag() + if isinstance(element, minidom.Element) and (element.tagName == 'Details'): + rc.URL = element.attributes["url"].value + childElements = [e for e in element.childNodes if isinstance(e, minidom.Element)] + if childElements: + for child in childElements: + key = child.tagName + if hasattr(rc, key): + if type(getattr(rc, key)) <> type([]): + setattr(rc, key, [getattr(rc, key)]) + setattr(rc, key, getattr(rc, key) + [unmarshal(child)]) + else: + setattr(rc, key, unmarshal(child)) + else: + rc = "".join([e.data for e in element.childNodes if isinstance(e, minidom.Text)]) + if element.tagName == 'SalesRank': + rc = int(rc.replace(',', '')) + return rc + +def buildURL(search_type, keyword, product_line, type, page, license_key): + url = "http://xml.amazon.com/onca/xml?v=1.0&f=xml&t=webservices-20" + url += "&dev-t=%s" % license_key.strip() + url += "&type=%s" % type + if page: + url += "&page=%s" % page + if product_line: + url += "&mode=%s" % product_line + url += "&%s=%s" % (search_type, urllib.quote(keyword)) +# print url + return url + +## main functions +def search(search_type, keyword, product_line, type="heavy", page=None, + license_key = None, http_proxy = None): + """search Amazon + + You need a license key to call this function; see + http://www.amazon.com/webservices + to get one. Then you can either pass it to + this function every time, or set it globally; see the module docs for details. + + Parameters: + keyword - keyword to search + search_type - in (KeywordSearch, BrowseNodeSearch, AsinSearch, UpcSearch, AuthorSearch, ArtistSearch, ActorSearch, DirectorSearch, ManufacturerSearch, ListManiaSearch, SimilaritySearch) + product_line - type of product to search for. restrictions based on search_type + UpcSearch - in (music, classical) + AuthorSearch - must be "books" + ArtistSearch - in (music, classical) + ActorSearch - in (dvd, vhs, video) + DirectorSearch - in (dvd, vhs, video) + ManufacturerSearch - in (electronics, kitchen, videogames, software, photo, pc-hardware) + http_proxy (optional) - address of HTTP proxy to use for sending and receiving SOAP messages + + Returns: list of Bags, each Bag may contain the following attributes: + Asin - Amazon ID ("ASIN" number) of this item + Authors - list of authors + Availability - "available", etc. + BrowseList - list of related categories + Catalog - catalog type ("Book", etc) + CollectiblePrice - ?, format "$34.95" + ImageUrlLarge - URL of large image of this item + ImageUrlMedium - URL of medium image of this item + ImageUrlSmall - URL of small image of this item + Isbn - ISBN number + ListPrice - list price, format "$34.95" + Lists - list of ListMania lists that include this item + Manufacturer - manufacturer + Media - media ("Paperback", "Audio CD", etc) + NumMedia - number of different media types in which this item is available + OurPrice - Amazon price, format "$24.47" + ProductName - name of this item + ReleaseDate - release date, format "09 April, 1999" + Reviews - reviews (AvgCustomerRating, plus list of CustomerReview with Rating, Summary, Content) + SalesRank - sales rank (integer) + SimilarProducts - list of Product, which is ASIN number + ThirdPartyNewPrice - ?, format "$34.95" + URL - URL of this item + """ + license_key = getLicense(license_key) + url = buildURL(search_type, keyword, product_line, type, page, license_key) + proxies = getProxies(http_proxy) + u = urllib.FancyURLopener(proxies) + usock = u.open(url) + xmldoc = minidom.parse(usock) + usock.close() + data = unmarshal(xmldoc).ProductInfo + if hasattr(data, 'ErrorMsg'): + raise AmazonError, data.ErrorMsg + else: + return data.Details + +def searchByKeyword(keyword, product_line="books", type="heavy", page=1, license_key=None, http_proxy=None): + return search("KeywordSearch", keyword, product_line, type, page, license_key, http_proxy) + +def browseBestSellers(browse_node, product_line="books", type="heavy", page=1, license_key=None, http_proxy=None): + return search("BrowseNodeSearch", browse_node, product_line, type, page, license_key, http_proxy) + +def searchByASIN(ASIN, type="heavy", license_key=None, http_proxy=None): + return search("AsinSearch", ASIN, None, type, None, license_key, http_proxy) + +def searchByUPC(UPC, type="heavy", license_key=None, http_proxy=None): + return search("UpcSearch", UPC, None, type, None, license_key, http_proxy) + +def searchByAuthor(author, type="heavy", page=1, license_key=None, http_proxy=None): + return search("AuthorSearch", author, "books", type, page, license_key, http_proxy) + +def searchByArtist(artist, product_line="music", type="heavy", page=1, license_key=None, http_proxy=None): + if product_line not in ("music", "classical"): + raise AmazonError, "product_line must be in ('music', 'classical')" + return search("ArtistSearch", artist, product_line, type, page, license_key, http_proxy) + +def searchByActor(actor, product_line="dvd", type="heavy", page=1, license_key=None, http_proxy=None): + if product_line not in ("dvd", "vhs", "video"): + raise AmazonError, "product_line must be in ('dvd', 'vhs', 'video')" + return search("ActorSearch", actor, product_line, type, page, license_key, http_proxy) + +def searchByDirector(director, product_line="dvd", type="heavy", page=1, license_key=None, http_proxy=None): + if product_line not in ("dvd", "vhs", "video"): + raise AmazonError, "product_line must be in ('dvd', 'vhs', 'video')" + return search("DirectorSearch", director, product_line, type, page, license_key, http_proxy) + +def searchByManufacturer(manufacturer, product_line="pc-hardware", type="heavy", page=1, license_key=None, http_proxy=None): + if product_line not in ("electronics", "kitchen", "videogames", "software", "photo", "pc-hardware"): + raise AmazonError, "product_line must be in ('electronics', 'kitchen', 'videogames', 'software', 'photo', 'pc-hardware')" + return search("ManufacturerSearch", manufacturer, product_line, type, page, license_key, http_proxy) + +def searchByListMania(listManiaID, type="heavy", page=1, license_key=None, http_proxy=None): + return search("ListManiaSearch", listManiaID, None, type, page, license_key, http_proxy) + +def searchSimilar(ASIN, type="heavy", page=1, license_key=None, http_proxy=None): + return search("SimilaritySearch", ASIN, None, type, page, license_key, http_proxy) +# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78: diff --git a/others/google.py b/others/google.py index 8e38ae538..78381657c 100644 --- a/others/google.py +++ b/others/google.py @@ -1,433 +1,433 @@ -"""Python wrapper for Google web APIs - -This module allows you to access Google's web APIs through SOAP, -to do things like search Google and get the results programmatically. -Described here: - http://www.google.com/apis/ - -You need a Google-provided license key to use these services. -Follow the link above to get one. These functions will look in -several places (in this order) for the license key: -- the "license_key" argument of each function -- the module-level LICENSE_KEY variable (call setLicense once to set it) -- an environment variable called GOOGLE_LICENSE_KEY -- a file called ".googlekey" in the current directory -- a file called "googlekey.txt" in the current directory -- a file called ".googlekey" in your home directory -- a file called "googlekey.txt" in your home directory -- a file called ".googlekey" in the same directory as google.py -- a file called "googlekey.txt" in the same directory as google.py - -Sample usage: ->>> import google ->>> google.setLicense('...') # must get your own key! ->>> data = google.doGoogleSearch('python') ->>> data.meta.searchTime -0.043221000000000002 ->>> data.results[0].URL -'http://www.python.org/' ->>> data.results[0].title -'Python Language Website' - -See documentation of SearchResultsMetaData and SearchResult classes -for other available attributes. -""" - -__author__ = "Mark Pilgrim (f8dy@diveintomark.org)" -__version__ = "0.5.2" -__cvsversion__ = "$Revision$"[11:-2] -__date__ = "$Date$"[7:-2] -__copyright__ = "Copyright (c) 2002 Mark Pilgrim" -__license__ = "Python" -__credits__ = """David Ascher, for the install script -Erik Max Francis, for the command line interface -Michael Twomey, for HTTP proxy support""" - -import SOAP -import os, sys, getopt - -LICENSE_KEY = None -HTTP_PROXY = None - -# don't touch the rest of these constants -class NoLicenseKey(Exception): pass -_url = 'http://api.google.com/search/beta2' -_namespace = 'urn:GoogleSearch' -_false = SOAP.booleanType(0) -_true = SOAP.booleanType(1) -_googlefile1 = ".googlekey" -_googlefile2 = "googlekey.txt" -_licenseLocations = ( - (lambda key: key, 'passed to the function in license_key variable'), - (lambda key: LICENSE_KEY, 'module-level LICENSE_KEY variable (call setLicense to set it)'), - (lambda key: os.environ.get('GOOGLE_LICENSE_KEY', None), 'an environment variable called GOOGLE_LICENSE_KEY'), - (lambda key: _contentsOf(os.getcwd(), _googlefile1), '%s in the current directory' % _googlefile1), - (lambda key: _contentsOf(os.getcwd(), _googlefile2), '%s in the current directory' % _googlefile2), - (lambda key: _contentsOf(os.environ.get('HOME', ''), _googlefile1), '%s in your home directory' % _googlefile1), - (lambda key: _contentsOf(os.environ.get('HOME', ''), _googlefile2), '%s in your home directory' % _googlefile2), - (lambda key: _contentsOf(_getScriptDir(), _googlefile1), '%s in the google.py directory' % _googlefile1), - (lambda key: _contentsOf(_getScriptDir(), _googlefile2), '%s in the google.py directory' % _googlefile2) - ) - -## administrative functions -def version(): - print """PyGoogle %(__version__)s -%(__copyright__)s -released %(__date__)s - -Thanks to: -%(__credits__)s""" % globals() - -def usage(): - program = os.path.basename(sys.argv[0]) - print """Usage: %(program)s [options] [querytype] query - -options: - -k, --key= Google license key (see important note below) - -1, -l, --lucky show only first hit - -m, --meta show meta information - -r, --reverse show results in reverse order - -x, --proxy= use HTTP proxy - -h, --help print this help - -v, --version print version and copyright information - -t, --test run test queries - -querytype: - -s, --search= search (default) - -c, --cache= retrieve cached page - -p, --spelling= check spelling - -IMPORTANT NOTE: all Google functions require a valid license key; -visit http://www.google.com/apis/ to get one. %(program)s will look in -these places (in order) and use the first license key it finds: - * the key specified on the command line""" % vars() - for get, location in _licenseLocations[2:]: - print " *", location - -## utility functions -def setLicense(license_key): - """set license key""" - global LICENSE_KEY - LICENSE_KEY = license_key - -def getLicense(license_key = None): - """get license key - - license key can come from any number of locations; - see module docs for search order""" - for get, location in _licenseLocations: - rc = get(license_key) - if rc: return rc - usage() - raise NoLicenseKey, 'get a license key at http://www.google.com/apis/' - -def setProxy(http_proxy): - """set HTTP proxy""" - global HTTP_PROXY - HTTP_PROXY = http_proxy - -def getProxy(http_proxy = None): - """get HTTP proxy""" - return http_proxy or HTTP_PROXY - -def _contentsOf(dirname, filename): - filename = os.path.join(dirname, filename) - if not os.path.exists(filename): return None - fsock = open(filename) - contents = fsock.read() - fsock.close() - return contents - -def _getScriptDir(): - if __name__ == '__main__': - return os.path.abspath(os.path.dirname(sys.argv[0])) - else: - return os.path.abspath(os.path.dirname(sys.modules[__name__].__file__)) - -def _marshalBoolean(value): - if value: - return _true - else: - return _false - -## output formatters -def makeFormatter(outputFormat): - classname = "%sOutputFormatter" % outputFormat.capitalize() - return globals()[classname]() - -def output(results, params): - formatter = makeFormatter(params.get("outputFormat", "text")) - outputmethod = getattr(formatter, params["func"]) - outputmethod(results, params) - -class OutputFormatter: - def boil(self, data): - if type(data) == type(u""): - return data.encode("ISO-8859-1", "replace") - else: - return data - -class TextOutputFormatter(OutputFormatter): - def common(self, data, params): - if params.get("showMeta", 0): - meta = data.meta - for category in meta.directoryCategories: - print "directoryCategory: %s" % self.boil(category["fullViewableName"]) - for attr in [node for node in dir(meta) if node <> "directoryCategories" and node[:2] <> '__']: - print "%s:" % attr, self.boil(getattr(meta, attr)) - - def doGoogleSearch(self, data, params): - results = data.results - if params.get("feelingLucky", 0): - results = results[:1] - if params.get("reverseOrder", 0): - results.reverse() - for result in results: - for attr in dir(result): - if attr == "directoryCategory": - print "directoryCategory:", self.boil(result.directoryCategory["fullViewableName"]) - elif attr[:2] <> '__': - print "%s:" % attr, self.boil(getattr(result, attr)) - print - self.common(data, params) - - def doGetCachedPage(self, data, params): - print data - self.common(data, params) - - doSpellingSuggestion = doGetCachedPage - -## search results classes -class _SearchBase: - def __init__(self, params): - for k, v in params.items(): - if isinstance(v, SOAP.structType): - v = v._asdict - try: - if isinstance(v[0], SOAP.structType): - v = [node._asdict for node in v] - except: - pass - self.__dict__[str(k)] = v - -class SearchResultsMetaData(_SearchBase): - """metadata of search query results - - Available attributes: - documentFiltering - flag indicates whether duplicate page filtering was perfomed in this search - searchComments - human-readable informational message (example: "'the' is a very common word - and was not included in your search") - estimatedTotalResultsCount - estimated total number of results for this query - estimateIsExact - flag indicates whether estimatedTotalResultsCount is an exact value - searchQuery - search string that initiated this search - startIndex - index of first result returned (zero-based) - endIndex - index of last result returned (zero-based) - searchTips - human-readable informational message on how to use Google bette - directoryCategories - list of dictionaries like this: - {'fullViewableName': Open Directory category, - 'specialEncoding': encoding scheme of this directory category} - searchTime - total search time, in seconds - """ - pass - -class SearchResult(_SearchBase): - """search result - - Available attributes: - URL - URL - title - title (HTML) - snippet - snippet showing query context (HTML) - cachedSize - size of cached version of this result, (KB) - relatedInformationPresent - flag indicates that the "related:" keyword is supported for this URL - hostName: When filtering occurs, a maximum of two results from any given host is returned. - When this occurs, the second resultElement that comes from that host contains - the host name in this parameter. - directoryCategory: dictionary like this: - {'fullViewableName': Open Directory category, - 'specialEncoding': encoding scheme of this directory category} - directoryTitle: Open Directory title of this result (or blank) - summary - Open Directory summary for this result (or blank) - """ - pass - -class SearchReturnValue: - """complete search results for a single query - - Available attributes: - meta - SearchResultsMetaData - results - list of SearchResult - """ - def __init__(self, metadata, results): - self.meta = metadata - self.results = results - -## main functions -def doGoogleSearch(q, start=0, maxResults=10, filter=1, restrict='', - safeSearch=0, language='', inputencoding='', outputencoding='', - license_key = None, http_proxy = None): - """search Google - - You need a license key to call this function; see - http://www.google.com/apis/ to get one. Then you can either pass it to - this function every time, or set it globally; see the module docs for details. - - Parameters: - q - search string. Anything you could type at google.com, you can pass here. - See http://www.google.com/help/features.html for examples of advanced features. - start (optional) - zero-based index of first desired result (for paging through - multiple pages of results) - maxResults (optional) - maximum number of results, currently capped at 10 - filter (optional) - set to 1 to filter out similar results, set to 0 to see everything - restrict (optional) - restrict results by country or topic. Examples: - Ukraine - search only sites located in Ukraine - linux - search Linux sites only - mac - search Mac sites only - bsd - search FreeBSD sites only - See the APIs_reference.html file in the SDK (http://www.google.com/apis/download.html) - for more advanced examples and a full list of country codes and topics. - safeSearch (optional) - set to 1 to filter results with SafeSearch (no adult material) - language (optional) - restricts search to documents in one or more languages. Example: - lang_en - only return pages in English - lang_fr - only return pages in French - See the APIs_reference.html file in the SDK (http://www.google.com/apis/download.html) - for more advanced examples and a full list of language codes. - inputencoding (optional) - sets the character encoding of q parameter - outputencoding (optional) - sets the character encoding of the returned results - See the APIs_reference.html file in the SDK (http://www.google.com/apis/download.html) - for a full list of encodings. - http_proxy (optional) - address of HTTP proxy to use for sending and receiving SOAP messages - - Returns: SearchReturnValue - .meta - SearchMetaData - .results - list of SearchResult - See documentation of these individual classes for list of available attributes - """ - http_proxy = getProxy(http_proxy) - remoteserver = SOAP.SOAPProxy(_url, namespace=_namespace, http_proxy=http_proxy) - license_key = getLicense(license_key) - filter = _marshalBoolean(filter) - safeSearch = _marshalBoolean(safeSearch) - data = remoteserver.doGoogleSearch(license_key, q, start, maxResults, filter, restrict, - safeSearch, language, inputencoding, outputencoding) - metadata = data._asdict - del metadata["resultElements"] - metadata = SearchResultsMetaData(metadata) - results = [SearchResult(node._asdict) for node in data.resultElements] - return SearchReturnValue(metadata, results) - -def doGetCachedPage(url, license_key = None, http_proxy = None): - """get page from Google cache - - You need a license key to call this function; see - http://www.google.com/apis/ to get one. Then you can either pass it to - this function every time, or set it globally; see the module docs for details. - - Parameters: - url - address of page to get - license_key (optional) - Google license key - http_proxy (optional) - address of HTTP proxy to use for sending and receiving SOAP messages - - Returns: string, text of cached page - """ - http_proxy = getProxy(http_proxy) - remoteserver = SOAP.SOAPProxy(_url, namespace=_namespace, http_proxy=http_proxy) - license_key = getLicense(license_key) - return remoteserver.doGetCachedPage(license_key, url) - -def doSpellingSuggestion(phrase, license_key = None, http_proxy = None): - """get spelling suggestions from Google - - You need a license key to call this function; see - http://www.google.com/apis/ to get one. Then you can either pass it to - this function every time, or set it globally; see the module docs for details. - - Parameters: - phrase - word or phrase to spell-check - http_proxy (optional) - address of HTTP proxy to use for sending and receiving SOAP messages - - Returns: text of suggested replacement, or None - """ - http_proxy = getProxy(http_proxy) - remoteserver = SOAP.SOAPProxy(_url, namespace=_namespace, http_proxy=http_proxy) - license_key = getLicense(license_key) - return remoteserver.doSpellingSuggestion(license_key, phrase) - -## functional test suite (see googletest.py for unit test suite) -def test(): - try: - getLicense(None) - except NoLicenseKey: - return - print "Searching for Python at google.com..." - data = doGoogleSearch("Python") - output(data, {"func": "doGoogleSearch"}) - - print "\nSearching for 5 _French_ pages about Python, encoded in ISO-8859-1..." - data = doGoogleSearch("Python", language='lang_fr', outputencoding='ISO-8859-1', maxResults=5) - output(data, {"func": "doGoogleSearch"}) - - phrase = "Pyhton programming languager" - print "\nTesting spelling suggetions for '%s'..." % phrase - data = doSpellingSuggestion(phrase) - output(data, {"func": "doSpellingSuggestion"}) - -## main driver for command-line use -def main(argv): - if not argv: - usage() - return - q = None - func = None - http_proxy = None - license_key = None - feelingLucky = 0 - showMeta = 0 - reverseOrder = 0 - runTest = 0 - outputFormat = "text" - try: - opts, args = getopt.getopt(argv, "s:c:p:k:lmrx:hvt1", - ["search=", "cache=", "spelling=", "key=", "lucky", "meta", "reverse", "proxy=", "help", "version", "test"]) - except getopt.GetoptError: - usage() - sys.exit(2) - for opt, arg in opts: - if opt in ("-s", "--search"): - q = arg - func = "doGoogleSearch" - elif opt in ("-c", "--cache"): - q = arg - func = "doGetCachedPage" - elif opt in ("-p", "--spelling"): - q = arg - func = "doSpellingSuggestion" - elif opt in ("-k", "--key"): - license_key = arg - elif opt in ("-l", "-1", "--lucky"): - feelingLucky = 1 - elif opt in ("-m", "--meta"): - showMeta = 1 - elif opt in ("-r", "--reverse"): - reverseOrder = 1 - elif opt in ("-x", "--proxy"): - http_proxy = arg - elif opt in ("-h", "--help"): - usage() - elif opt in ("-v", "--version"): - version() - elif opt in ("-t", "--test"): - runTest = 1 - if runTest: - setLicense(license_key) - setProxy(http_proxy) - test() - if args and not q: - q = args[0] - func = "doGoogleSearch" - if func: - results = globals()[func](q, http_proxy=http_proxy, license_key=license_key) - output(results, locals()) - -if __name__ == '__main__': - main(sys.argv[1:]) -# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78: +"""Python wrapper for Google web APIs + +This module allows you to access Google's web APIs through SOAP, +to do things like search Google and get the results programmatically. +Described here: + http://www.google.com/apis/ + +You need a Google-provided license key to use these services. +Follow the link above to get one. These functions will look in +several places (in this order) for the license key: +- the "license_key" argument of each function +- the module-level LICENSE_KEY variable (call setLicense once to set it) +- an environment variable called GOOGLE_LICENSE_KEY +- a file called ".googlekey" in the current directory +- a file called "googlekey.txt" in the current directory +- a file called ".googlekey" in your home directory +- a file called "googlekey.txt" in your home directory +- a file called ".googlekey" in the same directory as google.py +- a file called "googlekey.txt" in the same directory as google.py + +Sample usage: +>>> import google +>>> google.setLicense('...') # must get your own key! +>>> data = google.doGoogleSearch('python') +>>> data.meta.searchTime +0.043221000000000002 +>>> data.results[0].URL +'http://www.python.org/' +>>> data.results[0].title +'Python Language Website' + +See documentation of SearchResultsMetaData and SearchResult classes +for other available attributes. +""" + +__author__ = "Mark Pilgrim (f8dy@diveintomark.org)" +__version__ = "0.5.2" +__cvsversion__ = "$Revision$"[11:-2] +__date__ = "$Date$"[7:-2] +__copyright__ = "Copyright (c) 2002 Mark Pilgrim" +__license__ = "Python" +__credits__ = """David Ascher, for the install script +Erik Max Francis, for the command line interface +Michael Twomey, for HTTP proxy support""" + +import SOAP +import os, sys, getopt + +LICENSE_KEY = None +HTTP_PROXY = None + +# don't touch the rest of these constants +class NoLicenseKey(Exception): pass +_url = 'http://api.google.com/search/beta2' +_namespace = 'urn:GoogleSearch' +_false = SOAP.booleanType(0) +_true = SOAP.booleanType(1) +_googlefile1 = ".googlekey" +_googlefile2 = "googlekey.txt" +_licenseLocations = ( + (lambda key: key, 'passed to the function in license_key variable'), + (lambda key: LICENSE_KEY, 'module-level LICENSE_KEY variable (call setLicense to set it)'), + (lambda key: os.environ.get('GOOGLE_LICENSE_KEY', None), 'an environment variable called GOOGLE_LICENSE_KEY'), + (lambda key: _contentsOf(os.getcwd(), _googlefile1), '%s in the current directory' % _googlefile1), + (lambda key: _contentsOf(os.getcwd(), _googlefile2), '%s in the current directory' % _googlefile2), + (lambda key: _contentsOf(os.environ.get('HOME', ''), _googlefile1), '%s in your home directory' % _googlefile1), + (lambda key: _contentsOf(os.environ.get('HOME', ''), _googlefile2), '%s in your home directory' % _googlefile2), + (lambda key: _contentsOf(_getScriptDir(), _googlefile1), '%s in the google.py directory' % _googlefile1), + (lambda key: _contentsOf(_getScriptDir(), _googlefile2), '%s in the google.py directory' % _googlefile2) + ) + +## administrative functions +def version(): + print """PyGoogle %(__version__)s +%(__copyright__)s +released %(__date__)s + +Thanks to: +%(__credits__)s""" % globals() + +def usage(): + program = os.path.basename(sys.argv[0]) + print """Usage: %(program)s [options] [querytype] query + +options: + -k, --key= Google license key (see important note below) + -1, -l, --lucky show only first hit + -m, --meta show meta information + -r, --reverse show results in reverse order + -x, --proxy= use HTTP proxy + -h, --help print this help + -v, --version print version and copyright information + -t, --test run test queries + +querytype: + -s, --search= search (default) + -c, --cache= retrieve cached page + -p, --spelling= check spelling + +IMPORTANT NOTE: all Google functions require a valid license key; +visit http://www.google.com/apis/ to get one. %(program)s will look in +these places (in order) and use the first license key it finds: + * the key specified on the command line""" % vars() + for get, location in _licenseLocations[2:]: + print " *", location + +## utility functions +def setLicense(license_key): + """set license key""" + global LICENSE_KEY + LICENSE_KEY = license_key + +def getLicense(license_key = None): + """get license key + + license key can come from any number of locations; + see module docs for search order""" + for get, location in _licenseLocations: + rc = get(license_key) + if rc: return rc + usage() + raise NoLicenseKey, 'get a license key at http://www.google.com/apis/' + +def setProxy(http_proxy): + """set HTTP proxy""" + global HTTP_PROXY + HTTP_PROXY = http_proxy + +def getProxy(http_proxy = None): + """get HTTP proxy""" + return http_proxy or HTTP_PROXY + +def _contentsOf(dirname, filename): + filename = os.path.join(dirname, filename) + if not os.path.exists(filename): return None + fsock = open(filename) + contents = fsock.read() + fsock.close() + return contents + +def _getScriptDir(): + if __name__ == '__main__': + return os.path.abspath(os.path.dirname(sys.argv[0])) + else: + return os.path.abspath(os.path.dirname(sys.modules[__name__].__file__)) + +def _marshalBoolean(value): + if value: + return _true + else: + return _false + +## output formatters +def makeFormatter(outputFormat): + classname = "%sOutputFormatter" % outputFormat.capitalize() + return globals()[classname]() + +def output(results, params): + formatter = makeFormatter(params.get("outputFormat", "text")) + outputmethod = getattr(formatter, params["func"]) + outputmethod(results, params) + +class OutputFormatter: + def boil(self, data): + if type(data) == type(u""): + return data.encode("ISO-8859-1", "replace") + else: + return data + +class TextOutputFormatter(OutputFormatter): + def common(self, data, params): + if params.get("showMeta", 0): + meta = data.meta + for category in meta.directoryCategories: + print "directoryCategory: %s" % self.boil(category["fullViewableName"]) + for attr in [node for node in dir(meta) if node <> "directoryCategories" and node[:2] <> '__']: + print "%s:" % attr, self.boil(getattr(meta, attr)) + + def doGoogleSearch(self, data, params): + results = data.results + if params.get("feelingLucky", 0): + results = results[:1] + if params.get("reverseOrder", 0): + results.reverse() + for result in results: + for attr in dir(result): + if attr == "directoryCategory": + print "directoryCategory:", self.boil(result.directoryCategory["fullViewableName"]) + elif attr[:2] <> '__': + print "%s:" % attr, self.boil(getattr(result, attr)) + print + self.common(data, params) + + def doGetCachedPage(self, data, params): + print data + self.common(data, params) + + doSpellingSuggestion = doGetCachedPage + +## search results classes +class _SearchBase: + def __init__(self, params): + for k, v in params.items(): + if isinstance(v, SOAP.structType): + v = v._asdict + try: + if isinstance(v[0], SOAP.structType): + v = [node._asdict for node in v] + except: + pass + self.__dict__[str(k)] = v + +class SearchResultsMetaData(_SearchBase): + """metadata of search query results + + Available attributes: + documentFiltering - flag indicates whether duplicate page filtering was perfomed in this search + searchComments - human-readable informational message (example: "'the' is a very common word + and was not included in your search") + estimatedTotalResultsCount - estimated total number of results for this query + estimateIsExact - flag indicates whether estimatedTotalResultsCount is an exact value + searchQuery - search string that initiated this search + startIndex - index of first result returned (zero-based) + endIndex - index of last result returned (zero-based) + searchTips - human-readable informational message on how to use Google bette + directoryCategories - list of dictionaries like this: + {'fullViewableName': Open Directory category, + 'specialEncoding': encoding scheme of this directory category} + searchTime - total search time, in seconds + """ + pass + +class SearchResult(_SearchBase): + """search result + + Available attributes: + URL - URL + title - title (HTML) + snippet - snippet showing query context (HTML) + cachedSize - size of cached version of this result, (KB) + relatedInformationPresent - flag indicates that the "related:" keyword is supported for this URL + hostName: When filtering occurs, a maximum of two results from any given host is returned. + When this occurs, the second resultElement that comes from that host contains + the host name in this parameter. + directoryCategory: dictionary like this: + {'fullViewableName': Open Directory category, + 'specialEncoding': encoding scheme of this directory category} + directoryTitle: Open Directory title of this result (or blank) + summary - Open Directory summary for this result (or blank) + """ + pass + +class SearchReturnValue: + """complete search results for a single query + + Available attributes: + meta - SearchResultsMetaData + results - list of SearchResult + """ + def __init__(self, metadata, results): + self.meta = metadata + self.results = results + +## main functions +def doGoogleSearch(q, start=0, maxResults=10, filter=1, restrict='', + safeSearch=0, language='', inputencoding='', outputencoding='', + license_key = None, http_proxy = None): + """search Google + + You need a license key to call this function; see + http://www.google.com/apis/ to get one. Then you can either pass it to + this function every time, or set it globally; see the module docs for details. + + Parameters: + q - search string. Anything you could type at google.com, you can pass here. + See http://www.google.com/help/features.html for examples of advanced features. + start (optional) - zero-based index of first desired result (for paging through + multiple pages of results) + maxResults (optional) - maximum number of results, currently capped at 10 + filter (optional) - set to 1 to filter out similar results, set to 0 to see everything + restrict (optional) - restrict results by country or topic. Examples: + Ukraine - search only sites located in Ukraine + linux - search Linux sites only + mac - search Mac sites only + bsd - search FreeBSD sites only + See the APIs_reference.html file in the SDK (http://www.google.com/apis/download.html) + for more advanced examples and a full list of country codes and topics. + safeSearch (optional) - set to 1 to filter results with SafeSearch (no adult material) + language (optional) - restricts search to documents in one or more languages. Example: + lang_en - only return pages in English + lang_fr - only return pages in French + See the APIs_reference.html file in the SDK (http://www.google.com/apis/download.html) + for more advanced examples and a full list of language codes. + inputencoding (optional) - sets the character encoding of q parameter + outputencoding (optional) - sets the character encoding of the returned results + See the APIs_reference.html file in the SDK (http://www.google.com/apis/download.html) + for a full list of encodings. + http_proxy (optional) - address of HTTP proxy to use for sending and receiving SOAP messages + + Returns: SearchReturnValue + .meta - SearchMetaData + .results - list of SearchResult + See documentation of these individual classes for list of available attributes + """ + http_proxy = getProxy(http_proxy) + remoteserver = SOAP.SOAPProxy(_url, namespace=_namespace, http_proxy=http_proxy) + license_key = getLicense(license_key) + filter = _marshalBoolean(filter) + safeSearch = _marshalBoolean(safeSearch) + data = remoteserver.doGoogleSearch(license_key, q, start, maxResults, filter, restrict, + safeSearch, language, inputencoding, outputencoding) + metadata = data._asdict + del metadata["resultElements"] + metadata = SearchResultsMetaData(metadata) + results = [SearchResult(node._asdict) for node in data.resultElements] + return SearchReturnValue(metadata, results) + +def doGetCachedPage(url, license_key = None, http_proxy = None): + """get page from Google cache + + You need a license key to call this function; see + http://www.google.com/apis/ to get one. Then you can either pass it to + this function every time, or set it globally; see the module docs for details. + + Parameters: + url - address of page to get + license_key (optional) - Google license key + http_proxy (optional) - address of HTTP proxy to use for sending and receiving SOAP messages + + Returns: string, text of cached page + """ + http_proxy = getProxy(http_proxy) + remoteserver = SOAP.SOAPProxy(_url, namespace=_namespace, http_proxy=http_proxy) + license_key = getLicense(license_key) + return remoteserver.doGetCachedPage(license_key, url) + +def doSpellingSuggestion(phrase, license_key = None, http_proxy = None): + """get spelling suggestions from Google + + You need a license key to call this function; see + http://www.google.com/apis/ to get one. Then you can either pass it to + this function every time, or set it globally; see the module docs for details. + + Parameters: + phrase - word or phrase to spell-check + http_proxy (optional) - address of HTTP proxy to use for sending and receiving SOAP messages + + Returns: text of suggested replacement, or None + """ + http_proxy = getProxy(http_proxy) + remoteserver = SOAP.SOAPProxy(_url, namespace=_namespace, http_proxy=http_proxy) + license_key = getLicense(license_key) + return remoteserver.doSpellingSuggestion(license_key, phrase) + +## functional test suite (see googletest.py for unit test suite) +def test(): + try: + getLicense(None) + except NoLicenseKey: + return + print "Searching for Python at google.com..." + data = doGoogleSearch("Python") + output(data, {"func": "doGoogleSearch"}) + + print "\nSearching for 5 _French_ pages about Python, encoded in ISO-8859-1..." + data = doGoogleSearch("Python", language='lang_fr', outputencoding='ISO-8859-1', maxResults=5) + output(data, {"func": "doGoogleSearch"}) + + phrase = "Pyhton programming languager" + print "\nTesting spelling suggetions for '%s'..." % phrase + data = doSpellingSuggestion(phrase) + output(data, {"func": "doSpellingSuggestion"}) + +## main driver for command-line use +def main(argv): + if not argv: + usage() + return + q = None + func = None + http_proxy = None + license_key = None + feelingLucky = 0 + showMeta = 0 + reverseOrder = 0 + runTest = 0 + outputFormat = "text" + try: + opts, args = getopt.getopt(argv, "s:c:p:k:lmrx:hvt1", + ["search=", "cache=", "spelling=", "key=", "lucky", "meta", "reverse", "proxy=", "help", "version", "test"]) + except getopt.GetoptError: + usage() + sys.exit(2) + for opt, arg in opts: + if opt in ("-s", "--search"): + q = arg + func = "doGoogleSearch" + elif opt in ("-c", "--cache"): + q = arg + func = "doGetCachedPage" + elif opt in ("-p", "--spelling"): + q = arg + func = "doSpellingSuggestion" + elif opt in ("-k", "--key"): + license_key = arg + elif opt in ("-l", "-1", "--lucky"): + feelingLucky = 1 + elif opt in ("-m", "--meta"): + showMeta = 1 + elif opt in ("-r", "--reverse"): + reverseOrder = 1 + elif opt in ("-x", "--proxy"): + http_proxy = arg + elif opt in ("-h", "--help"): + usage() + elif opt in ("-v", "--version"): + version() + elif opt in ("-t", "--test"): + runTest = 1 + if runTest: + setLicense(license_key) + setProxy(http_proxy) + test() + if args and not q: + q = args[0] + func = "doGoogleSearch" + if func: + results = globals()[func](q, http_proxy=http_proxy, license_key=license_key) + output(results, locals()) + +if __name__ == '__main__': + main(sys.argv[1:]) +# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78: diff --git a/others/rfc822.py b/others/rfc822.py index 3c74111ad..3a1daaa74 100755 --- a/others/rfc822.py +++ b/others/rfc822.py @@ -387,7 +387,7 @@ class Message: def __contains__(self, name): return name.lower() in self.dict - + def __getitem__(self, name): """Get a specific header, as from a dictionary.""" return self.dict[name.lower()] diff --git a/others/rssparser.py b/others/rssparser.py index d5e3f5278..cf20920b5 100644 --- a/others/rssparser.py +++ b/others/rssparser.py @@ -1,519 +1,519 @@ -#!/usr/bin/python -"""Ultra-liberal RSS parser - -Visit http://diveintomark.org/projects/rss_parser/ for the latest version - -Handles RSS 0.9x and RSS 1.0 feeds - -RSS 0.9x elements: -- title, link, description, webMaster, managingEditor, language - copyright, lastBuildDate, pubDate - -RSS 1.0 elements: -- dc:rights, dc:language, dc:creator, dc:date, dc:subject, - content:encoded - -Things it handles that choke other RSS parsers: -- bastard combinations of RSS 0.9x and RSS 1.0 (most Movable Type feeds) -- illegal XML characters (most Radio feeds) -- naked and/or invalid HTML in description (The Register) -- content:encoded in item element (Aaron Swartz) -- guid in item element (Scripting News) -- fullitem in item element (Jon Udell) -- non-standard namespaces (BitWorking) - -Requires Python 2.2 or later -""" - -__author__ = "Mark Pilgrim (f8dy@diveintomark.org)" -__copyright__ = "Copyright 2002, Mark Pilgrim" -__contributors__ = ["Jason Diamond (jason@injektilo.org)"] -__license__ = "GPL" -__history__ = """ -1.0 - 9/27/2002 - MAP - fixed namespace processing on prefixed RSS 2.0 elements, - added Simon Fell's test suite -1.1 - 9/29/2002 - MAP - fixed infinite loop on incomplete CDATA sections -2.0 - 10/19/2002 - JD - use inchannel to watch out for image and textinput elements which can - also contain title, link, and description elements - JD - check for isPermaLink="false" attribute on guid elements - JD - replaced openAnything with open_resource supporting ETag and - If-Modified-Since request headers - JD - parse now accepts etag, modified, agent, and referrer optional - arguments - JD - modified parse to return a dictionary instead of a tuple so that any - etag or modified information can be returned and cached by the caller -2.0.1 - 10/21/2002 - MAP - changed parse() so that if we don't get anything - because of etag/modified, return the old etag/modified to the caller to - indicate why nothing is being returned -2.0.2 - 10/21/2002 - JB - added the inchannel to the if statement, otherwise its - useless. Fixes the problem JD was addressing by adding it. -2.1 - 11/14/2002 - MAP - added gzip support -2.2 - 1/27/2003 - MAP - added attribute support, admin:generatorAgent. - start_admingeneratoragent is an example of how to handle elements with - only attributes, no content. -""" - -try: - import timeoutsocket # http://www.timo-tasi.org/python/timeoutsocket.py - timeoutsocket.setDefaultSocketTimeout(10) -except ImportError: - pass -import cgi, re, sgmllib, string, StringIO, urllib, gzip -sgmllib.tagfind = re.compile('[a-zA-Z][-_.:a-zA-Z0-9]*') - -def decodeEntities(data): - data = data or '' - data = data.replace('<', '<') - data = data.replace('>', '>') - data = data.replace('"', '"') - data = data.replace(''', "'") - data = data.replace('&', '&') - return data - -class RSSParser(sgmllib.SGMLParser): - namespaces = {"http://backend.userland.com/rss": "", - "http://backend.userland.com/rss2": "", - "http://purl.org/rss/1.0/": "", - "http://purl.org/rss/1.0/modules/textinput/": "ti", - "http://purl.org/rss/1.0/modules/company/": "co", - "http://purl.org/rss/1.0/modules/syndication/": "sy", - "http://purl.org/dc/elements/1.1/": "dc", - "http://webns.net/mvcb/": "admin"} - - def reset(self): - self.channel = {} - self.items = [] - self.elementstack = [] - self.inchannel = 0 - self.initem = 0 - self.namespacemap = {} - sgmllib.SGMLParser.reset(self) - - def push(self, element, expectingText): - self.elementstack.append([element, expectingText, []]) - - def pop(self, element): - if not self.elementstack: return - if self.elementstack[-1][0] != element: return - element, expectingText, pieces = self.elementstack.pop() - if not expectingText: return - output = "".join(pieces) - output = decodeEntities(output) - if self.initem: - self.items[-1][element] = output - elif self.inchannel: - self.channel[element] = output - - def _addNamespaces(self, attrs): - for prefix, value in attrs: - if not prefix.startswith("xmlns:"): continue - prefix = prefix[6:] - if self.namespaces.has_key(value): - self.namespacemap[prefix] = self.namespaces[value] - - def _mapToStandardPrefix(self, name): - colonpos = name.find(':') - if colonpos <> -1: - prefix = name[:colonpos] - suffix = name[colonpos+1:] - prefix = self.namespacemap.get(prefix, prefix) - name = prefix + ':' + suffix - return name - - def _getAttribute(self, attrs, name): - value = [v for k, v in attrs if self._mapToStandardPrefix(k) == name] - if value: - value = value[0] - else: - value = None - return value - - def start_channel(self, attrs): - self.push('channel', 0) - self.inchannel = 1 - - def end_channel(self): - self.pop('channel') - self.inchannel = 0 - - def start_item(self, attrs): - self.items.append({}) - self.push('item', 0) - self.initem = 1 - - def end_item(self): - self.pop('item') - self.initem = 0 - - def start_dc_language(self, attrs): - self.push('language', 1) - start_language = start_dc_language - - def end_dc_language(self): - self.pop('language') - end_language = end_dc_language - - def start_dc_creator(self, attrs): - self.push('creator', 1) - start_managingeditor = start_dc_creator - start_webmaster = start_dc_creator - - def end_dc_creator(self): - self.pop('creator') - end_managingeditor = end_dc_creator - end_webmaster = end_dc_creator - - def start_dc_rights(self, attrs): - self.push('rights', 1) - start_copyright = start_dc_rights - - def end_dc_rights(self): - self.pop('rights') - end_copyright = end_dc_rights - - def start_dc_date(self, attrs): - self.push('date', 1) - start_lastbuilddate = start_dc_date - start_pubdate = start_dc_date - - def end_dc_date(self): - self.pop('date') - end_lastbuilddate = end_dc_date - end_pubdate = end_dc_date - - def start_dc_subject(self, attrs): - self.push('category', 1) - - def end_dc_subject(self): - self.pop('category') - - def start_link(self, attrs): - self.push('link', self.inchannel or self.initem) - - def end_link(self): - self.pop('link') - - def start_guid(self, attrs): - self.guidislink = ('ispermalink', 'false') not in attrs - self.push('guid', 1) - - def end_guid(self): - self.pop('guid') - if self.guidislink: - self.items[-1]['link'] = self.items[-1]['guid'] - - def start_title(self, attrs): - self.push('title', self.inchannel or self.initem) - - def start_description(self, attrs): - self.push('description', self.inchannel or self.initem) - - def start_content_encoded(self, attrs): - self.push('content_encoded', 1) - start_fullitem = start_content_encoded - - def end_content_encoded(self): - self.pop('content_encoded') - end_fullitem = end_content_encoded - - def start_admin_generatoragent(self, attrs): - self.push('generator', 1) - value = self._getAttribute(attrs, 'rdf:resource') - if value: - self.elementstack[-1][2].append(value) - self.pop('generator') - - def unknown_starttag(self, tag, attrs): - self._addNamespaces(attrs) - colonpos = tag.find(':') - if colonpos <> -1: - prefix = tag[:colonpos] - suffix = tag[colonpos+1:] - prefix = self.namespacemap.get(prefix, prefix) - if prefix: - prefix = prefix + '_' - methodname = 'start_' + prefix + suffix - try: - method = getattr(self, methodname) - return method(attrs) - except AttributeError: - return self.push(prefix + suffix, 0) - return self.push(tag, 0) - - def unknown_endtag(self, tag): - colonpos = tag.find(':') - if colonpos <> -1: - prefix = tag[:colonpos] - suffix = tag[colonpos+1:] - prefix = self.namespacemap.get(prefix, prefix) - if prefix: - prefix = prefix + '_' - methodname = 'end_' + prefix + suffix - try: - method = getattr(self, methodname) - return method() - except AttributeError: - return self.pop(prefix + suffix) - return self.pop(tag) - - def handle_charref(self, ref): - # called for each character reference, e.g. for " ", ref will be "160" - # Reconstruct the original character reference. - if not self.elementstack: return - self.elementstack[-1][2].append("&#%(ref)s;" % locals()) - - def handle_entityref(self, ref): - # called for each entity reference, e.g. for "©", ref will be "copy" - # Reconstruct the original entity reference. - if not self.elementstack: return - self.elementstack[-1][2].append("&%(ref)s;" % locals()) - - def handle_data(self, text): - # called for each block of plain text, i.e. outside of any tag and - # not containing any character or entity references - if not self.elementstack: return - self.elementstack[-1][2].append(text) - - def handle_comment(self, text): - # called for each comment, e.g. - pass - - def handle_pi(self, text): - # called for each processing instruction, e.g. - pass - - def handle_decl(self, text): - # called for the DOCTYPE, if present, e.g. - # - pass - - _new_declname_match = re.compile(r'[a-zA-Z][-_.a-zA-Z0-9:]*\s*').match - def _scan_name(self, i, declstartpos): - rawdata = self.rawdata - n = len(rawdata) - if i == n: - return None, -1 - m = self._new_declname_match(rawdata, i) - if m: - s = m.group() - name = s.strip() - if (i + len(s)) == n: - return None, -1 # end of buffer - return string.lower(name), m.end() - else: - self.updatepos(declstartpos, i) - self.error("expected name token") - - def parse_declaration(self, i): - # override internal declaration handler to handle CDATA blocks - if self.rawdata[i:i+9] == '', i) - if k == -1: k = len(self.rawdata) - self.handle_data(cgi.escape(self.rawdata[i+9:k])) - return k+3 - return sgmllib.SGMLParser.parse_declaration(self, i) - -def open_resource(source, etag=None, modified=None, agent=None, referrer=None): - """ - URI, filename, or string --> stream - - This function lets you define parsers that take any input source - (URL, pathname to local or network file, or actual data as a string) - and deal with it in a uniform manner. Returned object is guaranteed - to have all the basic stdio read methods (read, readline, readlines). - Just .close() the object when you're done with it. - - If the etag argument is supplied, it will be used as the value of an - If-None-Match request header. - - If the modified argument is supplied, it must be a tuple of 9 integers - as returned by gmtime() in the standard Python time module. This MUST - be in GMT (Greenwich Mean Time). The formatted date/time will be used - as the value of an If-Modified-Since request header. - - If the agent argument is supplied, it will be used as the value of a - User-Agent request header. - - If the referrer argument is supplied, it will be used as the value of a - Referer[sic] request header. - - The optional arguments are only used if the source argument is an HTTP - URL and the urllib2 module is importable (i.e., you must be using Python - version 2.0 or higher). - """ - - if hasattr(source, "read"): - return source - - if source == "-": - return sys.stdin - - # try to open with urllib2 (to use optional headers) - try: - import urllib2 - request = urllib2.Request(source) - if etag: - request.add_header("If-None-Match", etag) - if modified: - request.add_header("If-Modified-Since", format_http_date(modified)) - if agent: - request.add_header("User-Agent", agent) - if referrer: - # http://www.dictionary.com/search?q=referer - request.add_header("Referer", referrer) - request.add_header("Accept-encoding", "gzip") - try: - return urllib2.urlopen(request) - except urllib2.HTTPError: - # either the resource is not modified or some other HTTP - # error occurred so return an empty resource - return StringIO.StringIO("") - except: - # source must not be a valid URL but it might be a valid filename - pass - except ImportError: - # urllib2 isn't available so try to open with urllib - try: - return urllib.urlopen(source) - except: - # source still might be a filename - pass - - # try to open with native open function (if source is a filename) - try: - return open(source) - except: - pass - - # treat source as string - return StringIO.StringIO(str(source)) - -def get_etag(resource): - """ - Get the ETag associated with a response returned from a call to - open_resource(). - - If the resource was not returned from an HTTP server or the server did - not specify an ETag for the resource, this will return None. - """ - - if hasattr(resource, "info"): - return resource.info().getheader("ETag") - return None - -def get_modified(resource): - """ - Get the Last-Modified timestamp for a response returned from a call to - open_resource(). - - If the resource was not returned from an HTTP server or the server did - not specify a Last-Modified timestamp, this function will return None. - Otherwise, it returns a tuple of 9 integers as returned by gmtime() in - the standard Python time module(). - """ - - if hasattr(resource, "info"): - last_modified = resource.info().getheader("Last-Modified") - if last_modified: - return parse_http_date(last_modified) - return None - -short_weekdays = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"] -long_weekdays = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"] -months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] - -def format_http_date(date): - """ - Formats a tuple of 9 integers into an RFC 1123-compliant timestamp as - required in RFC 2616. We don't use time.strftime() since the %a and %b - directives can be affected by the current locale (HTTP dates have to be - in English). The date MUST be in GMT (Greenwich Mean Time). - """ - - return "%s, %02d %s %04d %02d:%02d:%02d GMT" % (short_weekdays[date[6]], date[2], months[date[1] - 1], date[0], date[3], date[4], date[5]) - -rfc1123_match = re.compile(r"(?P[A-Z][a-z]{2}), (?P\d{2}) (?P[A-Z][a-z]{2}) (?P\d{4}) (?P\d{2}):(?P\d{2}):(?P\d{2}) GMT").match -rfc850_match = re.compile(r"(?P[A-Z][a-z]+), (?P\d{2})-(?P[A-Z][a-z]{2})-(?P\d{2}) (?P\d{2}):(?P\d{2}):(?P\d{2}) GMT").match -asctime_match = re.compile(r"(?P[A-Z][a-z]{2}) (?P[A-Z][a-z]{2}) ?(?P\d\d?) (?P\d{2}):(?P\d{2}):(?P\d{2}) (?P\d{4})").match - -def parse_http_date(date): - """ - Parses any of the three HTTP date formats into a tuple of 9 integers as - returned by time.gmtime(). This should not use time.strptime() since - that function is not available on all platforms and could also be - affected by the current locale. - """ - - date = str(date) - year = 0 - weekdays = short_weekdays - - m = rfc1123_match(date) - if not m: - m = rfc850_match(date) - if m: - year = 1900 - weekdays = long_weekdays - else: - m = asctime_match(date) - if not m: - return None - - try: - year = year + int(m.group("year")) - month = months.index(m.group("month")) + 1 - day = int(m.group("day")) - hour = int(m.group("hour")) - minute = int(m.group("minute")) - second = int(m.group("second")) - weekday = weekdays.index(m.group("weekday")) - a = int((14 - month) / 12) - julian_day = (day - 32045 + int(((153 * (month + (12 * a) - 3)) + 2) / 5) + int((146097 * (year + 4800 - a)) / 400)) - (int((146097 * (year + 4799)) / 400) - 31738) + 1 - daylight_savings_flag = 0 - return (year, month, day, hour, minute, second, weekday, julian_day, daylight_savings_flag) - except: - # the month or weekday lookup probably failed indicating an invalid timestamp - return None - -def parse(uri, etag=None, modified=None, agent=None, referrer=None): - r = RSSParser() - f = open_resource(uri, etag=etag, modified=modified, agent=agent, referrer=referrer) - data = f.read() - if hasattr(f, "headers"): - if f.headers.get('content-encoding', None) == 'gzip': - data = gzip.GzipFile(fileobj=StringIO.StringIO(data)).read() - r.feed(data) - result = {"channel": r.channel, "items": r.items} - newEtag = get_etag(f) - if newEtag: result["etag"] = newEtag - elif etag: result["etag"] = etag - newModified = get_modified(f) - if newModified: result["modified"] = newModified - elif modified: result["modified"] = modified - f.close() - return result - -TEST_SUITE = ('http://www.pocketsoap.com/rssTests/rss1.0withModules.xml', - 'http://www.pocketsoap.com/rssTests/rss1.0withModulesNoDefNS.xml', - 'http://www.pocketsoap.com/rssTests/rss1.0withModulesNoDefNSLocalNameClash.xml', - 'http://www.pocketsoap.com/rssTests/rss2.0noNSwithModules.xml', - 'http://www.pocketsoap.com/rssTests/rss2.0noNSwithModulesLocalNameClash.xml', - 'http://www.pocketsoap.com/rssTests/rss2.0NSwithModules.xml', - 'http://www.pocketsoap.com/rssTests/rss2.0NSwithModulesNoDefNS.xml', - 'http://www.pocketsoap.com/rssTests/rss2.0NSwithModulesNoDefNSLocalNameClash.xml') - -if __name__ == '__main__': - import sys - if sys.argv[1:]: - urls = sys.argv[1:] - else: - urls = TEST_SUITE - from pprint import pprint - for url in urls: - print url - print - result = parse(url) - pprint(result['channel']) - print +#!/usr/bin/python +"""Ultra-liberal RSS parser + +Visit http://diveintomark.org/projects/rss_parser/ for the latest version + +Handles RSS 0.9x and RSS 1.0 feeds + +RSS 0.9x elements: +- title, link, description, webMaster, managingEditor, language + copyright, lastBuildDate, pubDate + +RSS 1.0 elements: +- dc:rights, dc:language, dc:creator, dc:date, dc:subject, + content:encoded + +Things it handles that choke other RSS parsers: +- bastard combinations of RSS 0.9x and RSS 1.0 (most Movable Type feeds) +- illegal XML characters (most Radio feeds) +- naked and/or invalid HTML in description (The Register) +- content:encoded in item element (Aaron Swartz) +- guid in item element (Scripting News) +- fullitem in item element (Jon Udell) +- non-standard namespaces (BitWorking) + +Requires Python 2.2 or later +""" + +__author__ = "Mark Pilgrim (f8dy@diveintomark.org)" +__copyright__ = "Copyright 2002, Mark Pilgrim" +__contributors__ = ["Jason Diamond (jason@injektilo.org)"] +__license__ = "GPL" +__history__ = """ +1.0 - 9/27/2002 - MAP - fixed namespace processing on prefixed RSS 2.0 elements, + added Simon Fell's test suite +1.1 - 9/29/2002 - MAP - fixed infinite loop on incomplete CDATA sections +2.0 - 10/19/2002 + JD - use inchannel to watch out for image and textinput elements which can + also contain title, link, and description elements + JD - check for isPermaLink="false" attribute on guid elements + JD - replaced openAnything with open_resource supporting ETag and + If-Modified-Since request headers + JD - parse now accepts etag, modified, agent, and referrer optional + arguments + JD - modified parse to return a dictionary instead of a tuple so that any + etag or modified information can be returned and cached by the caller +2.0.1 - 10/21/2002 - MAP - changed parse() so that if we don't get anything + because of etag/modified, return the old etag/modified to the caller to + indicate why nothing is being returned +2.0.2 - 10/21/2002 - JB - added the inchannel to the if statement, otherwise its + useless. Fixes the problem JD was addressing by adding it. +2.1 - 11/14/2002 - MAP - added gzip support +2.2 - 1/27/2003 - MAP - added attribute support, admin:generatorAgent. + start_admingeneratoragent is an example of how to handle elements with + only attributes, no content. +""" + +try: + import timeoutsocket # http://www.timo-tasi.org/python/timeoutsocket.py + timeoutsocket.setDefaultSocketTimeout(10) +except ImportError: + pass +import cgi, re, sgmllib, string, StringIO, urllib, gzip +sgmllib.tagfind = re.compile('[a-zA-Z][-_.:a-zA-Z0-9]*') + +def decodeEntities(data): + data = data or '' + data = data.replace('<', '<') + data = data.replace('>', '>') + data = data.replace('"', '"') + data = data.replace(''', "'") + data = data.replace('&', '&') + return data + +class RSSParser(sgmllib.SGMLParser): + namespaces = {"http://backend.userland.com/rss": "", + "http://backend.userland.com/rss2": "", + "http://purl.org/rss/1.0/": "", + "http://purl.org/rss/1.0/modules/textinput/": "ti", + "http://purl.org/rss/1.0/modules/company/": "co", + "http://purl.org/rss/1.0/modules/syndication/": "sy", + "http://purl.org/dc/elements/1.1/": "dc", + "http://webns.net/mvcb/": "admin"} + + def reset(self): + self.channel = {} + self.items = [] + self.elementstack = [] + self.inchannel = 0 + self.initem = 0 + self.namespacemap = {} + sgmllib.SGMLParser.reset(self) + + def push(self, element, expectingText): + self.elementstack.append([element, expectingText, []]) + + def pop(self, element): + if not self.elementstack: return + if self.elementstack[-1][0] != element: return + element, expectingText, pieces = self.elementstack.pop() + if not expectingText: return + output = "".join(pieces) + output = decodeEntities(output) + if self.initem: + self.items[-1][element] = output + elif self.inchannel: + self.channel[element] = output + + def _addNamespaces(self, attrs): + for prefix, value in attrs: + if not prefix.startswith("xmlns:"): continue + prefix = prefix[6:] + if self.namespaces.has_key(value): + self.namespacemap[prefix] = self.namespaces[value] + + def _mapToStandardPrefix(self, name): + colonpos = name.find(':') + if colonpos <> -1: + prefix = name[:colonpos] + suffix = name[colonpos+1:] + prefix = self.namespacemap.get(prefix, prefix) + name = prefix + ':' + suffix + return name + + def _getAttribute(self, attrs, name): + value = [v for k, v in attrs if self._mapToStandardPrefix(k) == name] + if value: + value = value[0] + else: + value = None + return value + + def start_channel(self, attrs): + self.push('channel', 0) + self.inchannel = 1 + + def end_channel(self): + self.pop('channel') + self.inchannel = 0 + + def start_item(self, attrs): + self.items.append({}) + self.push('item', 0) + self.initem = 1 + + def end_item(self): + self.pop('item') + self.initem = 0 + + def start_dc_language(self, attrs): + self.push('language', 1) + start_language = start_dc_language + + def end_dc_language(self): + self.pop('language') + end_language = end_dc_language + + def start_dc_creator(self, attrs): + self.push('creator', 1) + start_managingeditor = start_dc_creator + start_webmaster = start_dc_creator + + def end_dc_creator(self): + self.pop('creator') + end_managingeditor = end_dc_creator + end_webmaster = end_dc_creator + + def start_dc_rights(self, attrs): + self.push('rights', 1) + start_copyright = start_dc_rights + + def end_dc_rights(self): + self.pop('rights') + end_copyright = end_dc_rights + + def start_dc_date(self, attrs): + self.push('date', 1) + start_lastbuilddate = start_dc_date + start_pubdate = start_dc_date + + def end_dc_date(self): + self.pop('date') + end_lastbuilddate = end_dc_date + end_pubdate = end_dc_date + + def start_dc_subject(self, attrs): + self.push('category', 1) + + def end_dc_subject(self): + self.pop('category') + + def start_link(self, attrs): + self.push('link', self.inchannel or self.initem) + + def end_link(self): + self.pop('link') + + def start_guid(self, attrs): + self.guidislink = ('ispermalink', 'false') not in attrs + self.push('guid', 1) + + def end_guid(self): + self.pop('guid') + if self.guidislink: + self.items[-1]['link'] = self.items[-1]['guid'] + + def start_title(self, attrs): + self.push('title', self.inchannel or self.initem) + + def start_description(self, attrs): + self.push('description', self.inchannel or self.initem) + + def start_content_encoded(self, attrs): + self.push('content_encoded', 1) + start_fullitem = start_content_encoded + + def end_content_encoded(self): + self.pop('content_encoded') + end_fullitem = end_content_encoded + + def start_admin_generatoragent(self, attrs): + self.push('generator', 1) + value = self._getAttribute(attrs, 'rdf:resource') + if value: + self.elementstack[-1][2].append(value) + self.pop('generator') + + def unknown_starttag(self, tag, attrs): + self._addNamespaces(attrs) + colonpos = tag.find(':') + if colonpos <> -1: + prefix = tag[:colonpos] + suffix = tag[colonpos+1:] + prefix = self.namespacemap.get(prefix, prefix) + if prefix: + prefix = prefix + '_' + methodname = 'start_' + prefix + suffix + try: + method = getattr(self, methodname) + return method(attrs) + except AttributeError: + return self.push(prefix + suffix, 0) + return self.push(tag, 0) + + def unknown_endtag(self, tag): + colonpos = tag.find(':') + if colonpos <> -1: + prefix = tag[:colonpos] + suffix = tag[colonpos+1:] + prefix = self.namespacemap.get(prefix, prefix) + if prefix: + prefix = prefix + '_' + methodname = 'end_' + prefix + suffix + try: + method = getattr(self, methodname) + return method() + except AttributeError: + return self.pop(prefix + suffix) + return self.pop(tag) + + def handle_charref(self, ref): + # called for each character reference, e.g. for " ", ref will be "160" + # Reconstruct the original character reference. + if not self.elementstack: return + self.elementstack[-1][2].append("&#%(ref)s;" % locals()) + + def handle_entityref(self, ref): + # called for each entity reference, e.g. for "©", ref will be "copy" + # Reconstruct the original entity reference. + if not self.elementstack: return + self.elementstack[-1][2].append("&%(ref)s;" % locals()) + + def handle_data(self, text): + # called for each block of plain text, i.e. outside of any tag and + # not containing any character or entity references + if not self.elementstack: return + self.elementstack[-1][2].append(text) + + def handle_comment(self, text): + # called for each comment, e.g. + pass + + def handle_pi(self, text): + # called for each processing instruction, e.g. + pass + + def handle_decl(self, text): + # called for the DOCTYPE, if present, e.g. + # + pass + + _new_declname_match = re.compile(r'[a-zA-Z][-_.a-zA-Z0-9:]*\s*').match + def _scan_name(self, i, declstartpos): + rawdata = self.rawdata + n = len(rawdata) + if i == n: + return None, -1 + m = self._new_declname_match(rawdata, i) + if m: + s = m.group() + name = s.strip() + if (i + len(s)) == n: + return None, -1 # end of buffer + return string.lower(name), m.end() + else: + self.updatepos(declstartpos, i) + self.error("expected name token") + + def parse_declaration(self, i): + # override internal declaration handler to handle CDATA blocks + if self.rawdata[i:i+9] == '', i) + if k == -1: k = len(self.rawdata) + self.handle_data(cgi.escape(self.rawdata[i+9:k])) + return k+3 + return sgmllib.SGMLParser.parse_declaration(self, i) + +def open_resource(source, etag=None, modified=None, agent=None, referrer=None): + """ + URI, filename, or string --> stream + + This function lets you define parsers that take any input source + (URL, pathname to local or network file, or actual data as a string) + and deal with it in a uniform manner. Returned object is guaranteed + to have all the basic stdio read methods (read, readline, readlines). + Just .close() the object when you're done with it. + + If the etag argument is supplied, it will be used as the value of an + If-None-Match request header. + + If the modified argument is supplied, it must be a tuple of 9 integers + as returned by gmtime() in the standard Python time module. This MUST + be in GMT (Greenwich Mean Time). The formatted date/time will be used + as the value of an If-Modified-Since request header. + + If the agent argument is supplied, it will be used as the value of a + User-Agent request header. + + If the referrer argument is supplied, it will be used as the value of a + Referer[sic] request header. + + The optional arguments are only used if the source argument is an HTTP + URL and the urllib2 module is importable (i.e., you must be using Python + version 2.0 or higher). + """ + + if hasattr(source, "read"): + return source + + if source == "-": + return sys.stdin + + # try to open with urllib2 (to use optional headers) + try: + import urllib2 + request = urllib2.Request(source) + if etag: + request.add_header("If-None-Match", etag) + if modified: + request.add_header("If-Modified-Since", format_http_date(modified)) + if agent: + request.add_header("User-Agent", agent) + if referrer: + # http://www.dictionary.com/search?q=referer + request.add_header("Referer", referrer) + request.add_header("Accept-encoding", "gzip") + try: + return urllib2.urlopen(request) + except urllib2.HTTPError: + # either the resource is not modified or some other HTTP + # error occurred so return an empty resource + return StringIO.StringIO("") + except: + # source must not be a valid URL but it might be a valid filename + pass + except ImportError: + # urllib2 isn't available so try to open with urllib + try: + return urllib.urlopen(source) + except: + # source still might be a filename + pass + + # try to open with native open function (if source is a filename) + try: + return open(source) + except: + pass + + # treat source as string + return StringIO.StringIO(str(source)) + +def get_etag(resource): + """ + Get the ETag associated with a response returned from a call to + open_resource(). + + If the resource was not returned from an HTTP server or the server did + not specify an ETag for the resource, this will return None. + """ + + if hasattr(resource, "info"): + return resource.info().getheader("ETag") + return None + +def get_modified(resource): + """ + Get the Last-Modified timestamp for a response returned from a call to + open_resource(). + + If the resource was not returned from an HTTP server or the server did + not specify a Last-Modified timestamp, this function will return None. + Otherwise, it returns a tuple of 9 integers as returned by gmtime() in + the standard Python time module(). + """ + + if hasattr(resource, "info"): + last_modified = resource.info().getheader("Last-Modified") + if last_modified: + return parse_http_date(last_modified) + return None + +short_weekdays = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"] +long_weekdays = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"] +months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] + +def format_http_date(date): + """ + Formats a tuple of 9 integers into an RFC 1123-compliant timestamp as + required in RFC 2616. We don't use time.strftime() since the %a and %b + directives can be affected by the current locale (HTTP dates have to be + in English). The date MUST be in GMT (Greenwich Mean Time). + """ + + return "%s, %02d %s %04d %02d:%02d:%02d GMT" % (short_weekdays[date[6]], date[2], months[date[1] - 1], date[0], date[3], date[4], date[5]) + +rfc1123_match = re.compile(r"(?P[A-Z][a-z]{2}), (?P\d{2}) (?P[A-Z][a-z]{2}) (?P\d{4}) (?P\d{2}):(?P\d{2}):(?P\d{2}) GMT").match +rfc850_match = re.compile(r"(?P[A-Z][a-z]+), (?P\d{2})-(?P[A-Z][a-z]{2})-(?P\d{2}) (?P\d{2}):(?P\d{2}):(?P\d{2}) GMT").match +asctime_match = re.compile(r"(?P[A-Z][a-z]{2}) (?P[A-Z][a-z]{2}) ?(?P\d\d?) (?P\d{2}):(?P\d{2}):(?P\d{2}) (?P\d{4})").match + +def parse_http_date(date): + """ + Parses any of the three HTTP date formats into a tuple of 9 integers as + returned by time.gmtime(). This should not use time.strptime() since + that function is not available on all platforms and could also be + affected by the current locale. + """ + + date = str(date) + year = 0 + weekdays = short_weekdays + + m = rfc1123_match(date) + if not m: + m = rfc850_match(date) + if m: + year = 1900 + weekdays = long_weekdays + else: + m = asctime_match(date) + if not m: + return None + + try: + year = year + int(m.group("year")) + month = months.index(m.group("month")) + 1 + day = int(m.group("day")) + hour = int(m.group("hour")) + minute = int(m.group("minute")) + second = int(m.group("second")) + weekday = weekdays.index(m.group("weekday")) + a = int((14 - month) / 12) + julian_day = (day - 32045 + int(((153 * (month + (12 * a) - 3)) + 2) / 5) + int((146097 * (year + 4800 - a)) / 400)) - (int((146097 * (year + 4799)) / 400) - 31738) + 1 + daylight_savings_flag = 0 + return (year, month, day, hour, minute, second, weekday, julian_day, daylight_savings_flag) + except: + # the month or weekday lookup probably failed indicating an invalid timestamp + return None + +def parse(uri, etag=None, modified=None, agent=None, referrer=None): + r = RSSParser() + f = open_resource(uri, etag=etag, modified=modified, agent=agent, referrer=referrer) + data = f.read() + if hasattr(f, "headers"): + if f.headers.get('content-encoding', None) == 'gzip': + data = gzip.GzipFile(fileobj=StringIO.StringIO(data)).read() + r.feed(data) + result = {"channel": r.channel, "items": r.items} + newEtag = get_etag(f) + if newEtag: result["etag"] = newEtag + elif etag: result["etag"] = etag + newModified = get_modified(f) + if newModified: result["modified"] = newModified + elif modified: result["modified"] = modified + f.close() + return result + +TEST_SUITE = ('http://www.pocketsoap.com/rssTests/rss1.0withModules.xml', + 'http://www.pocketsoap.com/rssTests/rss1.0withModulesNoDefNS.xml', + 'http://www.pocketsoap.com/rssTests/rss1.0withModulesNoDefNSLocalNameClash.xml', + 'http://www.pocketsoap.com/rssTests/rss2.0noNSwithModules.xml', + 'http://www.pocketsoap.com/rssTests/rss2.0noNSwithModulesLocalNameClash.xml', + 'http://www.pocketsoap.com/rssTests/rss2.0NSwithModules.xml', + 'http://www.pocketsoap.com/rssTests/rss2.0NSwithModulesNoDefNS.xml', + 'http://www.pocketsoap.com/rssTests/rss2.0NSwithModulesNoDefNSLocalNameClash.xml') + +if __name__ == '__main__': + import sys + if sys.argv[1:]: + urls = sys.argv[1:] + else: + urls = TEST_SUITE + from pprint import pprint + for url in urls: + print url + print + result = parse(url) + pprint(result['channel']) + print diff --git a/others/unittest.py b/others/unittest.py index 6d39632bb..a2b58c6ad 100644 --- a/others/unittest.py +++ b/others/unittest.py @@ -263,7 +263,7 @@ class TestCase: def _fail(self, msg): """Underlying implementation of failure.""" raise self.failureException, msg - + def fail(self, msg=None): """Fail immediately, with the given message.""" global asserts diff --git a/plugins/Alias.py b/plugins/Alias.py index 457d44af1..bf181c41a 100644 --- a/plugins/Alias.py +++ b/plugins/Alias.py @@ -66,7 +66,7 @@ def findBiggestDollar(alias): return int(dollars[-1]) else: return None - + def makeNewAlias(name, alias): if findAliasCommand(name, alias): raise RecursiveAlias @@ -105,7 +105,7 @@ class Alias(callbacks.Privmsg): def __init__(self): callbacks.Privmsg.__init__(self) self.frozen = sets.Set() - + def freeze(self, irc, msg, args): """ @@ -133,7 +133,7 @@ class Alias(callbacks.Privmsg): else: irc.error(msg, 'There is no such alias.') unfreeze = privmsgs.checkCapability(unfreeze, 'admin') - + def alias(self, irc, msg, args): """ @@ -164,7 +164,7 @@ class Alias(callbacks.Privmsg): print debug.exnToString(e) except: print 'exception raised' - + def unalias(self, irc, msg, args): """ @@ -181,8 +181,8 @@ class Alias(callbacks.Privmsg): irc.error(msg, 'That alias is frozen.') else: irc.error(msg, 'There is no such alias.') - - + + Class = Alias diff --git a/plugins/ChannelDB.py b/plugins/ChannelDB.py index c8d038d83..0305d1485 100644 --- a/plugins/ChannelDB.py +++ b/plugins/ChannelDB.py @@ -111,7 +111,7 @@ class ChannelDB(callbacks.PrivmsgCommandAndRegexp, ChannelDBHandler): )""") db.commit() return db - + def doPrivmsg(self, irc, msg): callbacks.PrivmsgCommandAndRegexp.doPrivmsg(self, irc, msg) if ircutils.isChannel(msg.args[0]): @@ -174,7 +174,7 @@ class ChannelDB(callbacks.PrivmsgCommandAndRegexp, ChannelDBHandler): except KeyError: pass db.commit() - + def doPart(self, irc, msg): channel = msg.args[0] db = self.getDb(channel) diff --git a/plugins/Ctcp.py b/plugins/Ctcp.py index 73d14ec4f..dd7cf35b3 100644 --- a/plugins/Ctcp.py +++ b/plugins/Ctcp.py @@ -30,7 +30,7 @@ ### """ -Handles standard CTCP responses to PING, TIME, SOURCE, VERSION, USERINFO, +Handles standard CTCP responses to PING, TIME, SOURCE, VERSION, USERINFO, and FINGER. """ diff --git a/plugins/Factoids.py b/plugins/Factoids.py index 18da24b75..9f11f5f17 100644 --- a/plugins/Factoids.py +++ b/plugins/Factoids.py @@ -30,7 +30,7 @@ ### """ -Handles "factoids," little tidbits of information held in a database and +Handles "factoids," little tidbits of information held in a database and available on demand via several commands. Commands include: @@ -59,7 +59,7 @@ class Factoids(ChannelDBHandler, callbacks.Privmsg): def __init__(self): ChannelDBHandler.__init__(self) callbacks.Privmsg.__init__(self) - + def makeDb(self, filename): if os.path.exists(filename): return sqlite.connect(filename) @@ -120,7 +120,7 @@ class Factoids(ChannelDBHandler, callbacks.Privmsg): irc.reply(msg, conf.replySuccess) else: irc.error(msg, 'That factoid is locked.') - + def lookup(self, irc, msg, args): "[] (If not sent in the channel itself) []" channel = privmsgs.getChannel(msg, args) @@ -141,7 +141,7 @@ class Factoids(ChannelDBHandler, callbacks.Privmsg): else: factoid = results[number][0] irc.reply(msg, '%s/%s: %s' % (key, number, factoid)) - + def lock(self, irc, msg, args): "[] (If not sent in the channel itself) " channel = privmsgs.getChannel(msg, args) @@ -155,7 +155,7 @@ class Factoids(ChannelDBHandler, callbacks.Privmsg): irc.reply(msg, conf.replySuccess) else: irc.error(msg, conf.replyNoCapability % capability) - + def unlock(self, irc, msg, args): "[] (If not sent in the channel itself) " channel = privmsgs.getChannel(msg, args) @@ -183,7 +183,7 @@ class Factoids(ChannelDBHandler, callbacks.Privmsg): irc.reply(msg, conf.replySuccess) else: irc.error(msg, conf.replyNoCapability % capability) - + def randomfactoid(self, irc, msg, args): "[] (If not sent in the channel itself)" channel = privmsgs.getChannel(msg, args) @@ -226,7 +226,7 @@ class Factoids(ChannelDBHandler, callbacks.Privmsg): s = 'Key %r is %s and has %s factoids associated with it: %s' % \ (key, locked and 'locked' or 'not locked', counter, '; '.join(L)) irc.reply(msg, s) - + Class = Factoids diff --git a/plugins/FreeBSD.py b/plugins/FreeBSD.py index 9010e7fff..1a631976f 100755 --- a/plugins/FreeBSD.py +++ b/plugins/FreeBSD.py @@ -204,8 +204,8 @@ class FreeBSD(callbacks.Privmsg): 'Please narrow your search.' % cursor.rowcount) else: irc.reply(msg, ', '.join(names)) - - + + def numports(self, irc, msg, args): """takes no arguments @@ -249,10 +249,10 @@ class FreeBSD(callbacks.Privmsg): categories = map(lambda t: t[0], cursor.fetchall()) irc.reply(msg, '%s; Categories: %s; Maintainer: %s; Website: %s' % (info, ', '.join(categories), maintainer, website)) - + Class = FreeBSD - + if __name__ == '__main__': makeDb(dbFile, getIndex(), replace=True) diff --git a/plugins/Friendly.py b/plugins/Friendly.py index cdec2c58e..1ef74ecd2 100755 --- a/plugins/Friendly.py +++ b/plugins/Friendly.py @@ -52,7 +52,7 @@ class Friendly(callbacks.PrivmsgRegexp): if match.group(1) == irc.nick: irc.queueMsg(ircmsgs.privmsg(msg.args[0], '%s!' % msg.nick)) - + Class = Friendly # vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78: diff --git a/plugins/FunCommands.py b/plugins/FunCommands.py index a290e13ed..8666fc353 100644 --- a/plugins/FunCommands.py +++ b/plugins/FunCommands.py @@ -122,7 +122,7 @@ class FunCommands(callbacks.Privmsg): irc.reply(msg, chr(i)) except ValueError: irc.error(msg, 'That number doesn\'t map to an 8-bit character.') - + def base(self, irc, msg, args): """ @@ -154,7 +154,7 @@ class FunCommands(callbacks.Privmsg): LL.reverse() L.extend(LL) irc.reply(msg, ''.join(L)) - + def encode(self, irc, msg, args): """ @@ -173,7 +173,7 @@ class FunCommands(callbacks.Privmsg): """ encoding, text = privmsgs.getArgs(args, needed=2) irc.reply(msg, text.decode(encoding).encode('utf-8')) - + def hexlify(self, irc, msg, args): """ @@ -323,7 +323,7 @@ class FunCommands(callbacks.Privmsg): text = text.replace('x', 'kth') text = text.replace('X', 'KTH') irc.reply(msg, text) - + _leettrans = string.maketrans('oOaAeElBTiIts', '004433187!1+5') _leetres = ((re.compile(r'\b(?:(?:[yY][o0O][oO0uU])|u)\b'), 'j00'), (re.compile(r'fear'), 'ph33r'), @@ -405,7 +405,7 @@ class FunCommands(callbacks.Privmsg): return '%s*i' % imag else: return '%s+%si' % (real, imag) - + def calc(self, irc, msg, args): """ @@ -540,7 +540,7 @@ class FunCommands(callbacks.Privmsg): def lastfrom(self, irc, msg, args): """[] - Returns the last message in from . is only + Returns the last message in from . is only necessary if the message isn't sent in the channel itself. """ channel = privmsgs.getChannel(msg, args) @@ -616,7 +616,7 @@ class FunCommands(callbacks.Privmsg): irc.error(msg, 'That function has no documentation.') return irc.reply(msg, s) - + Class = FunCommands diff --git a/plugins/FunDB.py b/plugins/FunDB.py index 5363afa89..2dcd04f4e 100755 --- a/plugins/FunDB.py +++ b/plugins/FunDB.py @@ -104,7 +104,7 @@ def addWord(db, word, commit=False): WHERE word=%s))""", word, sorted) if commit: db.commit() - + class FunDB(callbacks.Privmsg): """ @@ -119,7 +119,7 @@ class FunDB(callbacks.Privmsg): def die(self): self.db.commit() self.db.close() - + ''' def praise(self, irc, msg, args): """ @@ -182,7 +182,7 @@ class FunDB(callbacks.Privmsg): irc.error(msg, 'There is no such insult.') else: irc.reply(msg, cursor.fetchone()[0]) - + def addinsult(self, irc, msg, args): """ @@ -267,7 +267,7 @@ class FunDB(callbacks.Privmsg): irc.error(msg, 'There is no such excuse.') else: irc.reply(msg, cursor.fetchone()[0]) - + def addexcuse(self, irc, msg, args): """ @@ -294,7 +294,7 @@ class FunDB(callbacks.Privmsg): cursor.execute("""DELETE FROM excuses WHERE id=%s""", id) self.db.commit() irc.reply(msg, conf.replySuccess) - + def numexcuses(self, irc, msg, args): """takes no arguments @@ -343,7 +343,7 @@ class FunDB(callbacks.Privmsg): irc.error(msg, 'There is no such lart.') else: irc.reply(msg, cursor.fetchone()[0]) - + def addlart(self, irc, msg, args): """ @@ -377,7 +377,7 @@ class FunDB(callbacks.Privmsg): def numlarts(self, irc, msg, args): """takes no arguments - + Returns the number of larts currently in the database. """ cursor = self.db.cursor() @@ -443,7 +443,7 @@ class FunDB(callbacks.Privmsg): else: (city, state) = cursor.fetchone() irc.reply(msg, '%s, %s' % (city, state)) - + def zipcodefor(self, irc, msg, args): """ @@ -474,7 +474,7 @@ class FunDB(callbacks.Privmsg): random.shuffle(zipcodes) irc.reply(msg, '(%s shown of %s): %s' % \ (len(zipcodes), cursor.rowcount, ', '.join(zipcodes))) - + Class = FunDB @@ -520,5 +520,5 @@ if __name__ == '__main__': zipcode, city, state) db.commit() db.close() - -# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78: + +# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78: diff --git a/plugins/Gameknot.py b/plugins/Gameknot.py index 6cdf4cb17..de64447c3 100644 --- a/plugins/Gameknot.py +++ b/plugins/Gameknot.py @@ -116,8 +116,8 @@ class Gameknot(callbacks.PrivmsgCommandAndRegexp): raise callbacks.Error, 'The format of the page was odd.' except urllib2.URLError: raise callbacks.Error, 'Couldn\'t connect to gameknot.com' - - + + def gkstats(self, irc, msg, args): """ diff --git a/plugins/Http.py b/plugins/Http.py index 4bde35d2a..6827f0e5a 100644 --- a/plugins/Http.py +++ b/plugins/Http.py @@ -212,7 +212,7 @@ class Http(callbacks.Privmsg): irc.error(msg, 'Couldn\'t open search page.') ''' - + _tempregex = re.compile('CLASS=obsTempTextA>(\d+)°F',\ re.IGNORECASE) _cityregex = re.compile(r'Local Forecast for (.*), (.*?) ') @@ -263,7 +263,7 @@ class Http(callbacks.Privmsg): quote = utils.htmlToText(m.group(1)) quote = ' // '.join(quote.splitlines()) irc.reply(msg, quote) - + _acronymre = re.compile(r']+>(?:)?([^<]+)(?:)?') def acronym(self, irc, msg, args): """ @@ -321,7 +321,7 @@ class Http(callbacks.Privmsg): _debBranches = ('stable', 'testing', 'unstable', 'experimental') def debversion(self, irc, msg, args): """ [stable|testing|unstable|experimental] - + Returns the current version(s) of a Debian package in the given branch (if any, otherwise all available ones are displayed). """ @@ -363,7 +363,7 @@ class Http(callbacks.Privmsg): (numberOfPackages, len(responses), ', '.join(responses)) irc.reply(msg, s) - + Class = Http diff --git a/plugins/Infobot.py b/plugins/Infobot.py index db81b7e67..3b115cefb 100755 --- a/plugins/Infobot.py +++ b/plugins/Infobot.py @@ -131,7 +131,7 @@ class Infobot(callbacks.PrivmsgRegexp): irc.queueMsg(ircmsgs.privmsg(nick, s)) except KeyError: irc.reply(msg, 'I don\'t know anything about %s' % key) - + def factoid(self, irc, msg, match): r"^(no[ :,-]+)?(.+?)\s+(was|is|am|were|are)\s+(also\s+)?(.+?)(?!\?+)$" (correction, key, isAre, addition, value) = match.groups() @@ -151,7 +151,7 @@ class Infobot(callbacks.PrivmsgRegexp): else: self.insertFactoid(key, isAre, value) irc.reply(msg, self.getRandomSaying('confirms')) - + def unknown(self, irc, msg, match): r"^(.+?)\?[?.! ]*$" key = match.group(1) @@ -169,12 +169,12 @@ class Infobot(callbacks.PrivmsgRegexp): numAre = cursor.fetchone()[0] s = 'I have %s is factoids and %s are factoids' % (numIs, numAre) irc.reply(msg, s) - - + + Class = Infobot - + if __name__ == '__main__': import sys if len(sys.argv) < 2 and sys.argv[1] not in ('is', 'are'): @@ -202,5 +202,5 @@ if __name__ == '__main__': print 'Invalid line (%s): %r' %(debug.exnToString(e),line) db.commit() - + # vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78: diff --git a/plugins/KillBold.py b/plugins/KillBold.py index 861f65ea6..a1195afe9 100644 --- a/plugins/KillBold.py +++ b/plugins/KillBold.py @@ -53,7 +53,7 @@ class KillBold(callbacks.Privmsg): return ircmsgs.privmsg(msg.args[0],msg.args[1].replace('\x02', '')) else: return msg - + Class = KillBold # vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78: diff --git a/plugins/NickServ.py b/plugins/NickServ.py index a6f8882fe..51a02fa50 100644 --- a/plugins/NickServ.py +++ b/plugins/NickServ.py @@ -39,12 +39,14 @@ Commands include: from baseplugin import * import re +import time import conf import ircdb import ircmsgs import privmsgs import ircutils +import schedule import callbacks def configure(onStart, afterConnect, advanced): @@ -55,20 +57,21 @@ def configure(onStart, afterConnect, advanced): onStart.append('startnickserv %s %s' % (nick, password)) class NickServ(privmsgs.CapabilityCheckingPrivmsg): - capability = 'owner' + capability = 'admin' def __init__(self): callbacks.Privmsg.__init__(self) self.nickserv = '' - + def startnickserv(self, irc, msg, args): - " " + " " \ + "] + + Attempts to get ops from ChanServ in . If no channel is + given, the current channel is assumed. + """ + channel = privmsgs.getChannel(msg, args) + irc.sendMsg(ircmsgs.privmsg(self.chanserv, 'op %s' % channel)) + Class = NickServ diff --git a/plugins/Notes.py b/plugins/Notes.py index bc1acdc91..20ac6f58d 100644 --- a/plugins/Notes.py +++ b/plugins/Notes.py @@ -79,20 +79,20 @@ class Notes(callbacks.Privmsg): note TEXT )""") self.db.commit() - + def _addUser(self, username): "Not callable from channel, used to add users to database." cursor = self.db.cursor() cursor.execute('INSERT INTO users VALUES (NULL, %s)', username) self.db.commit() - + def getUserId(self, username): "Returns the user id matching the given username from the users table." cursor = self.db.cursor() cursor.execute('SELECT id FROM users where name=%s', username) if cursor.rowcount != 0: return cursor.fetchone()[0] - else: + else: raise KeyError, username def getUserName(self, userid): @@ -116,7 +116,7 @@ class Notes(callbacks.Privmsg): "Called when module is unloaded/reloaded." self.db.commit() self.db.close() - + def doJoin(self, irc, msg): try: name = ircdb.users.getUserName(msg.prefix) @@ -151,7 +151,7 @@ class Notes(callbacks.Privmsg): def sendnote(self, irc, msg, args): """ - + Sends a new note to the user specified. """ (name, note) = privmsgs.getArgs(args, needed=2) @@ -165,13 +165,13 @@ class Notes(callbacks.Privmsg): self._addUser(recipient) senderId = self.getUserId(sender) recipId = self.getUserId(recipient) - if ircutils.isChannel(msg.args[0]): + if ircutils.isChannel(msg.args[0]): public = 1 - else: - public = 0 + else: + public = 0 cursor = self.db.cursor() - cursor.execute("""INSERT INTO notes VALUES - (NULL, %s, %s, %s, 0, 0, %s, %s)""", + cursor.execute("""INSERT INTO notes VALUES + (NULL, %s, %s, %s, 0, 0, %s, %s)""", senderId, recipId, int(time.time()), public, note) self.db.commit() @@ -179,7 +179,7 @@ class Notes(callbacks.Privmsg): def note(self, irc, msg, args): """ - + Retrieves a single note by unique note id. """ noteid = privmsgs.getArgs(args) @@ -191,7 +191,7 @@ class Notes(callbacks.Privmsg): return cursor = self.db.cursor() cursor.execute("""SELECT notes.note, notes.to_id, notes.from_id, - notes.added_at, notes.public + notes.added_at, notes.public FROM users, notes WHERE users.name=%s AND notes.to_id=users.id AND @@ -216,7 +216,7 @@ class Notes(callbacks.Privmsg): def notes(self, irc, msg, args): """takes no arguments - + Retrieves all your unread notes. """ try: @@ -275,7 +275,7 @@ class Notes(callbacks.Privmsg): ircutils.shrinkList(ids, ', ', 425) ids.reverse() irc.reply(msg, ', '.join(ids)) - + Class = Notes diff --git a/plugins/Quotes.py b/plugins/Quotes.py index 68940c074..a6c550797 100644 --- a/plugins/Quotes.py +++ b/plugins/Quotes.py @@ -179,7 +179,7 @@ class Quotes(ChannelDBHandler, callbacks.Privmsg): irc.reply(msg, conf.replySuccess) else: irc.error(msg, conf.replyNoCapability % capability) - + Class = Quotes # vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78: diff --git a/plugins/RSS.py b/plugins/RSS.py index 5808b7e9a..e2c11aa8a 100644 --- a/plugins/RSS.py +++ b/plugins/RSS.py @@ -84,7 +84,7 @@ def configure(onStart, afterConnect, advanced): if infocmd: onStart.append('alias %s "rssinfo %s"' % (infocmd, url)) onStart.append('freeze %s' % infocmd) - + class RSS(callbacks.Privmsg): threaded = True @@ -114,7 +114,7 @@ class RSS(callbacks.Privmsg): irc.error(msg, 'Error grabbing RSS feed') return irc.reply(msg, payload) - + def rssinfo(self, irc, msg, args): """ @@ -142,7 +142,7 @@ class RSS(callbacks.Privmsg): # The rest of the entries are all available in the channel key response = 'Title: %s; URL: <%s>; ' \ 'Description: %s; Last updated %s.' % ( - info.get('title', 'unavailable').strip(), + info.get('title', 'unavailable').strip(), info.get('link', 'unavailable').strip(), info.get('description', 'unavailable').strip(), when) diff --git a/plugins/Relay.py b/plugins/Relay.py index 2e12148bc..125ff1f66 100644 --- a/plugins/Relay.py +++ b/plugins/Relay.py @@ -87,7 +87,7 @@ def configure(onStart, afterConnect, advanced): while yn('Would like to relay between any more channels?') == 'y': channel = anything('What channel?') afterConnect.append('relayjoin %s' % channel) - + class Relay(callbacks.Privmsg): def __init__(self): @@ -99,7 +99,7 @@ class Relay(callbacks.Privmsg): self.lastmsg = ircmsgs.ping('this is just a fake message') self.channels = sets.Set() self.abbreviations = {} - + def inFilter(self, irc, msg): if not isinstance(irc, irclib.Irc): irc = irc.getRealIrc() @@ -110,7 +110,7 @@ class Relay(callbacks.Privmsg): self.ircstates[irc].addMsg(irc, self.lastmsg) self.lastmsg = msg return msg - + def startrelay(self, irc, msg, args): """ @@ -131,7 +131,7 @@ class Relay(callbacks.Privmsg): def relayconnect(self, irc, msg, args): """ (port defaults to 6667) - + Connects to another network at . The network abbreviation is used when relaying messages from that network to other networks. @@ -267,7 +267,7 @@ class Relay(callbacks.Privmsg): do312 = do311 do317 = do311 do319 = do311 - + def do318(self, irc, msg): if not isinstance(irc, irclib.Irc): irc = irc.getRealIrc() @@ -289,7 +289,7 @@ class Relay(callbacks.Privmsg): s = '%s (%s) has been online since %s (idle for %s) and is on %s' % \ (nick, hostmask, signon, idle, channels) replyIrc.reply(replyMsg, s) - + def _formatPrivmsg(self, nick, network, msg): # colorize nicks nick = ircutils.mircColor(nick, *ircutils.canonicalColor(nick)) @@ -358,7 +358,7 @@ class Relay(callbacks.Privmsg): for otherIrc in self.ircs.itervalues(): if otherIrc != irc: otherIrc.queueMsg(ircmsgs.privmsg(channel, s)) - + def doNick(self, irc, msg): if self.started: if not isinstance(irc, irclib.Irc): @@ -386,7 +386,7 @@ class Relay(callbacks.Privmsg): for otherIrc in self.ircs.itervalues(): if otherIrc != irc: otherIrc.queueMsg(ircmsgs.privmsg(channel, s)) - + def outFilter(self, irc, msg): if not self.started: return msg @@ -419,9 +419,9 @@ class Relay(callbacks.Privmsg): if otherIrc != irc: if otherIrc.state.getTopic(channel) != topic: otherIrc.queueMsg(ircmsgs.topic(channel, topic)) - + return msg Class = Relay - + # vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78: diff --git a/plugins/ThreadedFunCommands.py b/plugins/ThreadedFunCommands.py index 3e8d6d1fc..537b055ce 100644 --- a/plugins/ThreadedFunCommands.py +++ b/plugins/ThreadedFunCommands.py @@ -75,6 +75,6 @@ class ThreadedFunCommands(callbacks.Privmsg): beta = version.strip() irc.reply(msg, 'The latest stable kernel is %s; ' \ 'the latest beta kernel is %s.' % (stable, beta)) - + Class = ThreadedFunCommands # vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78: diff --git a/plugins/Topic.py b/plugins/Topic.py index cbc7fb482..c0de92ca7 100644 --- a/plugins/Topic.py +++ b/plugins/Topic.py @@ -119,7 +119,7 @@ class Topic(callbacks.Privmsg): except IndexError: irc.error(msg, 'That\'s not a valid index.') return - + def changetopic(self, irc, msg, args): """[] @@ -159,7 +159,7 @@ class Topic(callbacks.Privmsg): topics.insert(number, newTopic) newTopic = self.topicSeparator.join(topics) irc.queueMsg(ircmsgs.topic(channel, newTopic)) - + def removetopic(self, irc, msg, args): "[] (if not sent in the channel itself) " channel = privmsgs.getChannel(msg, args) diff --git a/plugins/TwistedCommands.py b/plugins/TwistedCommands.py index 037466abd..f4fa7197d 100644 --- a/plugins/TwistedCommands.py +++ b/plugins/TwistedCommands.py @@ -58,7 +58,7 @@ class TwistedCommands(callbacks.Privmsg): failure.printDetailedTraceback() irc.error(msg, failure.getErrorMessage()) return errback - + dictnumberre = re.compile('^\d+:\s*(.*)$') def dictCallback(self, irc, msg, word): def formatDictResults(definitions): @@ -91,7 +91,7 @@ class TwistedCommands(callbacks.Privmsg): deferred = dict.define('dict.org', 2628, 'wn', word) deferred.addCallback(self.dictCallback(irc, msg, word)) deferred.addErrback(self.defaultErrback(irc, msg)) - + class TwistedRegexp(callbacks.PrivmsgRegexp): def dccrecv(self, irc, msg, match): diff --git a/plugins/URLSnarfer.py b/plugins/URLSnarfer.py index 3d68e540d..253177459 100644 --- a/plugins/URLSnarfer.py +++ b/plugins/URLSnarfer.py @@ -187,8 +187,8 @@ class URLSnarfer(callbacks.Privmsg, ChannelDBHandler): else: (url,) = cursor.fetchone() irc.reply(msg, url) - - + + Class = URLSnarfer # vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78: diff --git a/plugins/Unix.py b/plugins/Unix.py index 0f3153962..49f275c6e 100644 --- a/plugins/Unix.py +++ b/plugins/Unix.py @@ -61,7 +61,7 @@ def configure(onStart, afterConnect, advanced): print 'choose to install it later, and then the module will' print 'automatically work, as long as it is in the path of the' print 'user that supybot runs under.' - print + print print 'The "progstats" command can reveal potentially sensitive' print 'information about your machine. Here\'s an example of its output:' @@ -80,7 +80,7 @@ def progstats(): os.getcwd(), " ".join(sys.argv), sys.version.translate(string.ascii, '\r\n')) return response - + class Unix(callbacks.Privmsg): def __init__(self): @@ -117,7 +117,7 @@ class Unix(callbacks.Privmsg): except KeyError: name = '(unknown)' irc.reply(msg, '%s (#%s): %s' % (name, i, os.strerror(i))) - + def progstats(self, irc, msg, args): """takes no arguments @@ -144,7 +144,7 @@ class Unix(callbacks.Privmsg): salt = makeSalt() irc.reply(msg, crypt.crypt(password, salt)) - def spell(self, irc, msg, args): + def spell(self, irc, msg, args): """ Returns the result of passing to aspell/ispell. The results @@ -158,7 +158,7 @@ class Unix(callbacks.Privmsg): return self._spellWrite.write(word) self._spellWrite.write('\n') - line = self._spellRead.readline() + line = self._spellRead.readline() # aspell puts extra whitespace, ignore it while line == '\n': line = self._spellRead.readline() @@ -178,7 +178,7 @@ class Unix(callbacks.Privmsg): else: resp = 'Something unexpected was seen in the [ai]spell output.' irc.reply(msg, resp) - + Class = Unix # vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78: diff --git a/plugins/Utilities.py b/plugins/Utilities.py index c4dfae6c7..6cd2a0461 100644 --- a/plugins/Utilities.py +++ b/plugins/Utilities.py @@ -48,11 +48,11 @@ def configure(onStart, afterConnect, advanced): class Utilities(callbacks.Privmsg): def ignore(self, irc, msg, args): pass - + def shrink(self, irc, msg, args): text = privmsgs.getArgs(args) irc.reply(msg, text[:400]) - + def strjoin(self, irc, msg, args): " " sep = args.pop(0) @@ -128,7 +128,7 @@ class Utilities(callbacks.Privmsg): irc.error(msg, 'Invalid regexp: %s' % e.args[0]) return irc.reply(msg, ' '.join(r.findall(text))) - - + + Class = Utilities # vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78: diff --git a/scripts/setup.py b/scripts/setup.py index e786ed5b5..fe3f6b039 100644 --- a/scripts/setup.py +++ b/scripts/setup.py @@ -170,5 +170,5 @@ if __name__ == '__main__': print 'You\'re done! Now run the bot with the command line:' print 'src/bot.py conf/%s' % name print - + # vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78: diff --git a/src/MiscCommands.py b/src/MiscCommands.py index 788ebbc20..bd648dac0 100755 --- a/src/MiscCommands.py +++ b/src/MiscCommands.py @@ -131,7 +131,7 @@ class MiscCommands(callbacks.Privmsg): (command, simplehelp)) else: irc.error(msg, 'That command has no help at all.') - + def bug(self, irc, msg, args): """takes no arguments diff --git a/src/asyncoreDrivers.py b/src/asyncoreDrivers.py index 9a133c0db..420a19087 100644 --- a/src/asyncoreDrivers.py +++ b/src/asyncoreDrivers.py @@ -126,7 +126,7 @@ class AsyncoreDriver(asynchat.async_chat, object): def die(self): self.close() - + class ReplListener(asyncore.dispatcher, object): def __init__(self, port=conf.telnetPort): asyncore.dispatcher.__init__(self) diff --git a/src/callbacks.py b/src/callbacks.py index 73ac26534..a9d6266dd 100644 --- a/src/callbacks.py +++ b/src/callbacks.py @@ -98,7 +98,7 @@ def reply(msg, s): if len(m) > 512: m = reply(msg, 'My response would\'ve been too long.') return m - + class RateLimiter: lastRequest = {} def __init__(self): @@ -232,8 +232,8 @@ def tokenize(s): raise SyntaxError, str(e) debug.msg('tokenize took %s seconds.' % (time.time() - start), 'verbose') return args - - + + class IrcObjectProxy: def __init__(self, irc, msg, args): @@ -340,7 +340,7 @@ class CommandThread(threading.Thread): self.irc = irc self.msg = msg self.setDaemon(True) - + def run(self): try: start = time.time() @@ -356,7 +356,7 @@ class CommandThread(threading.Thread): debug.recoverableException() self.irc.error(self.msg, debug.exnToString(e)) - + class Privmsg(irclib.IrcCallback): """Base class for all Privmsg handlers.""" threaded = False @@ -508,7 +508,7 @@ class PrivmsgCommandAndRegexp(Privmsg): method = getattr(self, name) r = re.compile(method.__doc__, self.flags) self.res.append((r, method)) - + def doPrivmsg(self, irc, msg): if ircdb.checkIgnored(msg.prefix, msg.args[0]): return @@ -528,10 +528,10 @@ class PrivmsgCommandAndRegexp(Privmsg): if msg: args = tokenize(s) self.Proxy(irc, msg, args) - - - - - + + + + + # vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78: diff --git a/src/conf.py b/src/conf.py index 4c87a589f..45125eebd 100644 --- a/src/conf.py +++ b/src/conf.py @@ -188,3 +188,6 @@ driverModule = 'asyncoreDrivers' ############################### ############################### # vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78: + + + diff --git a/src/debug.py b/src/debug.py index d8127ae55..ba840bda3 100644 --- a/src/debug.py +++ b/src/debug.py @@ -45,7 +45,7 @@ except ImportError: class conf: logDir = '.' detailedTracebacks = True - + import world ### diff --git a/src/fix.py b/src/fix.py index b428eb452..51d15ba88 100644 --- a/src/fix.py +++ b/src/fix.py @@ -284,5 +284,5 @@ def partition(p, L): def flip((x, y)): return (y, x) - + # vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78: diff --git a/src/ircdb.py b/src/ircdb.py index 2e43b5461..d5f7ec047 100644 --- a/src/ircdb.py +++ b/src/ircdb.py @@ -152,7 +152,7 @@ class UserCapabilitySet(CapabilitySet): capability = ircutils.toLower(capability) assert capability != '!owner', '"!owner" disallowed.' CapabilitySet.add(self, capability) - + class IrcUser(object): """This class holds the capabilities and authentications for a user. """ @@ -498,7 +498,7 @@ def _x(capability, ret): return not ret else: return ret - + def checkCapability(hostmask, capability, users=users, channels=channels): #debug.printf('*** checking %s for %s' % (hostmask, capability)) if world.startup: @@ -560,7 +560,7 @@ def checkCapability(hostmask, capability, users=users, channels=channels): else: #debug.printf('returning appropriate value given no good reason') return _x(capability, conf.defaultAllow) - + def checkCapabilities(hostmask, capabilities, requireAll=False): """Checks that a user has capabilities in a list. diff --git a/src/irclib.py b/src/irclib.py index 550007539..d9995b413 100644 --- a/src/irclib.py +++ b/src/irclib.py @@ -227,7 +227,7 @@ class IrcState(IrcCommandDispatcher): def __ne__(self, other): return not self == other - + def copy(self): ret = self.__class__() ret.history = copy.copy(self.history) diff --git a/src/ircmsgs.py b/src/ircmsgs.py index 891a96c4f..d99a94a8e 100644 --- a/src/ircmsgs.py +++ b/src/ircmsgs.py @@ -112,7 +112,7 @@ class IrcMsg(object): else: (self.nick, self.user, self.host) = (self.prefix,)*3 self.args = tuple(self.args) - + def __str__(self): if self._str is not None: return self._str @@ -155,7 +155,7 @@ class IrcMsg(object): self._len += len(arg) + 1 # Remember space prior to the arg. self._len += 2 # For colon before the prefix and before the last arg. return self._len - + def __eq__(self, other): return hash(self) == hash(other) and \ self.command == other.command and \ @@ -249,7 +249,7 @@ def prettyPrint(msg, addRecipients=False): elif msg.command == 'TOPIC': s = '*** %s changes topic to %s' % (nickorprefix(), msg.args[1]) return s - + ### # Various IrcMsg functions ### diff --git a/src/ircutils.py b/src/ircutils.py index a8349a647..f9c2eb470 100644 --- a/src/ircutils.py +++ b/src/ircutils.py @@ -309,7 +309,7 @@ class IrcString(str): def __str__(self): return str(self.original) - + def __eq__(self, s): try: return toLower(s) == self.lowered diff --git a/src/privmsgs.py b/src/privmsgs.py index a20701626..45e282c62 100644 --- a/src/privmsgs.py +++ b/src/privmsgs.py @@ -100,7 +100,7 @@ def getKeywordArgs(irc, msg, d=None): args.append(left) del args[0] # The command name itself. return (args, d) - + def checkCapability(f, capability): def newf(self, irc, msg, args): if ircdb.checkCapability(msg.prefix, capability): @@ -225,7 +225,7 @@ class OwnerCommands(CapabilityCheckingPrivmsg): """ world.upkeep() irc.reply(msg, conf.replySuccess) - + def set(self, irc, msg, args): """ @@ -280,7 +280,7 @@ class OwnerCommands(CapabilityCheckingPrivmsg): world.superReload(__import__(name)) irc.reply(msg, conf.replySuccess) ''' - + def reload(self, irc, msg, args): """ @@ -307,7 +307,7 @@ class OwnerCommands(CapabilityCheckingPrivmsg): irc.error(msg, 'No plugin %s exists.' % name) else: irc.error(msg, 'There was no callback %s.' % name) - + def unload(self, irc, msg, args): """ diff --git a/src/schedule.py b/src/schedule.py index 17e88923d..6a457735e 100644 --- a/src/schedule.py +++ b/src/schedule.py @@ -47,7 +47,7 @@ class RTuple(tuple): return not tuple.__le__(self, other) def __cmp__(self, other): return -1*tuple.__cmp__(self, other) - + class Schedule(drivers.IrcDriver): def __init__(self): drivers.IrcDriver.__init__(self) diff --git a/src/structures.py b/src/structures.py index 017f03996..e15be9505 100644 --- a/src/structures.py +++ b/src/structures.py @@ -66,10 +66,10 @@ class RingBuffer(object): return False return True return False - + def __nonzero__(self): return len(self) > 0 - + def __contains__(self, elt): return elt in self.L @@ -88,7 +88,7 @@ class RingBuffer(object): def extend(self, seq): for elt in seq: self.append(elt) - + def __getitem__(self, idx): if self.full: oidx = idx @@ -226,7 +226,7 @@ class queue(object): return self.front[-(idx+1)] else: return self.back[(idx-len(self.front))] - + def __setitem__(self, oidx, value): if len(self) == 0: raise IndexError, 'queue index out of range' @@ -272,7 +272,7 @@ class queue(object): self.front = L self.back = [] - + class MaxLengthQueue(queue): __slots__ = ('length',) def __init__(self, length, seq=()): @@ -285,7 +285,7 @@ class MaxLengthQueue(queue): def __setstate__(self, (length, q)): self.length = length queue.__setstate__(self, q) - + def enqueue(self, elt): queue.enqueue(self, elt) if len(self) > self.length: diff --git a/src/twistedDrivers.py b/src/twistedDrivers.py index 52b938033..3b97f3804 100644 --- a/src/twistedDrivers.py +++ b/src/twistedDrivers.py @@ -87,7 +87,7 @@ class SupyIrcProtocol(LineReceiver): self.transport.loseConnection() reconnect = die - + class SupyReconnectingFactory(ReconnectingClientFactory): maxDelay = 600 @@ -96,7 +96,7 @@ class SupyReconnectingFactory(ReconnectingClientFactory): self.irc = irc self.server = (server, port) reactor.connectTCP(server, port, self) - + class MyShell(Shell): def checkUserAndPass(self, username, password): @@ -112,13 +112,13 @@ class MyShell(Shell): return False except KeyError: return False - + class MyShellFactory(ShellFactory): protocol = MyShell if conf.telnetEnable and __name__ != '__main__': reactor.listenTCP(conf.telnetPort, MyShellFactory()) - + Driver = SupyReconnectingFactory diff --git a/test/test.py b/test/test.py index 7742ea91e..fbb3d917f 100755 --- a/test/test.py +++ b/test/test.py @@ -160,8 +160,8 @@ class PluginTestCase(unittest.TestCase): self.assertEqual(len(expectedResponses), len(responses)) for (m, expected) in zip(responses, expectedResponses): self.assertEqual(m.args[1], expected) - - + + if __name__ == '__main__': world.testing = True if len(sys.argv) > 1: diff --git a/test/test_FunCommands.py b/test/test_FunCommands.py index 77d298609..837e572a6 100644 --- a/test/test_FunCommands.py +++ b/test/test_FunCommands.py @@ -78,7 +78,7 @@ class FunCommandsTest(PluginTestCase): def testPydoc(self): self.assertNotError('pydoc str') self.assertError('pydoc foobar') - + def testOrd(self): for c in map(chr, range(256)): i = ord(c) diff --git a/test/test_ircdb.py b/test/test_ircdb.py index 56f0840d7..bbd69a7e7 100644 --- a/test/test_ircdb.py +++ b/test/test_ircdb.py @@ -103,7 +103,7 @@ class UserCapabilitySetTestCase(unittest.TestCase): d.add('owner') self.failUnless(d.check('owner')) - + class CapabilitySetTestCase(unittest.TestCase): def testContains(self): @@ -119,7 +119,7 @@ class CapabilitySetTestCase(unittest.TestCase): s.add('!foo') self.failUnless('foo' in s) self.failUnless('!foo' in s) - + def testCheck(self): s = ircdb.CapabilitySet() self.assertRaises(KeyError, s.check, 'foo') @@ -146,7 +146,7 @@ class CapabilitySetTestCase(unittest.TestCase): s.add('foo') self.failUnless(s.check('foo')) self.failIf(s.check('!foo')) - + class UserCapabilitySetTestCase(unittest.TestCase): def testOwner(self): @@ -179,7 +179,7 @@ class IrcUserTestCase(unittest.TestCase): u.addCapability('owner') self.failUnless(u.checkCapability('foo')) self.failIf(u.checkCapability('!foo')) - + def testInitCapabilities(self): u = ircdb.IrcUser(capabilities=['foo']) self.failUnless(u.checkCapability('foo')) @@ -211,7 +211,7 @@ class IrcUserTestCase(unittest.TestCase): u = ircdb.IrcUser(ignore=True) self.failIf(u.checkCapability('foo')) self.failUnless(u.checkCapability('!foo')) - + class IrcChannelTestCase(unittest.TestCase): def testInit(self): c = ircdb.IrcChannel() @@ -240,7 +240,7 @@ class IrcChannelTestCase(unittest.TestCase): def testLobotomized(self): c = ircdb.IrcChannel(lobotomized=True) self.failUnless(c.checkIgnored('')) - + def testIgnored(self): prefix = 'foo!bar@baz' banmask = ircutils.banmask(prefix) @@ -262,10 +262,10 @@ class UsersDictionaryTestCase(unittest.TestCase): fd.write('{}\n') fd.close() self.users = ircdb.UsersDictionary(self.filename) - + def tearDown(self): os.remove(self.filename) - + def testGetSetDelUser(self): self.assertRaises(KeyError, self.users.getUser, 'foo') self.assertRaises(KeyError, self.users.getUser, 'foo!bar@baz') @@ -283,7 +283,7 @@ class UsersDictionaryTestCase(unittest.TestCase): u.removeHostmask(banmask) u.addHostmask('*!*@*') self.assertRaises(ValueError, self.users.setUser, 'biff', u) - + class CheckCapabilityTestCase(unittest.TestCase): filename = 'CheckCapabilityTestCase.conf' @@ -334,7 +334,7 @@ class CheckCapabilityTestCase(unittest.TestCase): self.users.setUser('antichanfoo', antichanfoo) channel = ircdb.IrcChannel() self.channels.setChannel(self.channel, channel) - + def tearDown(self): os.remove(self.filename) @@ -383,10 +383,10 @@ class CheckCapabilityTestCase(unittest.TestCase): def testJustChanFoo(self): self.channels.setChannel(self.channel, self.channelnothing) self.failUnless(self.checkCapability(self.justchanfoo, self.chancap)) - self.failIf(self.checkCapability(self.justchanfoo, self.antichancap)) + self.failIf(self.checkCapability(self.justchanfoo, self.antichancap)) self.channelnothing.defaultAllow = not self.channelnothing.defaultAllow self.failUnless(self.checkCapability(self.justchanfoo, self.chancap)) - self.failIf(self.checkCapability(self.justchanfoo, self.antichancap)) + self.failIf(self.checkCapability(self.justchanfoo, self.antichancap)) self.channels.setChannel(self.channel, self.channelanticap) self.failUnless(self.checkCapability(self.justchanfoo, self.chancap)) self.failIf(self.checkCapability(self.justchanfoo, self.antichancap)) diff --git a/test/test_irclib.py b/test/test_irclib.py index 568cac085..19b0125f5 100644 --- a/test/test_irclib.py +++ b/test/test_irclib.py @@ -50,7 +50,7 @@ class IrcMsgQueueTestCase(unittest.TestCase): def testEmpty(self): q = irclib.IrcMsgQueue() self.failIf(q) - + def testEnqueueDequeue(self): q = irclib.IrcMsgQueue() q.enqueue(self.msg) @@ -131,7 +131,7 @@ class ChannelTestCase(unittest.TestCase): self.failIf('quuz' in c.halfops) self.failIf('quuz' in c.voices) - + class IrcStateTestCase(unittest.TestCase): class FakeIrc: nick = 'nick' @@ -171,11 +171,11 @@ class IrcStateTestCase(unittest.TestCase): self.assertEqual(state1, state2) except Exception: pass - + """ def testChannels(self): - channel = + channel = state = irclib.IrcState() state.addMsg(self.irc, ircmsgs.join('#foo')) """ diff --git a/test/test_ircmsgs.py b/test/test_ircmsgs.py index dd371b887..3d9721689 100644 --- a/test/test_ircmsgs.py +++ b/test/test_ircmsgs.py @@ -126,7 +126,7 @@ class FunctionsTestCase(unittest.TestCase): withException = ircmsgs.ban(channel, ban, exception) self.assertEqual(ircutils.separateModes(withException.args[1:]), [('+b', ban), ('+e', exception)]) - + def testBans(self): channel = '#osu' bans = ['*!*@*', 'jemfinch!*@*'] diff --git a/test/test_ircutils.py b/test/test_ircutils.py index 8cdd5fea0..919690e8e 100644 --- a/test/test_ircutils.py +++ b/test/test_ircutils.py @@ -84,14 +84,14 @@ class FunctionsTestCase(unittest.TestCase): self.assertEqual('\x03,5foo\x03', ircutils.mircColor(s, bg='brown')) self.assertEqual('\x036,7foo\x03', ircutils.mircColor(s, bg='orange', fg='purple')) - + def testMircColors(self): # Make sure all (k, v) pairs are also (v, k) pairs. for (k, v) in ircutils.mircColors.items(): if k: self.assertEqual(ircutils.mircColors[v], k) - - + + def testSafeArgument(self): s = 'I have been running for 9 seconds' bolds = ircutils.bold(s) diff --git a/test/test_schedule.py b/test/test_schedule.py index af00f0300..d8b137447 100644 --- a/test/test_schedule.py +++ b/test/test_schedule.py @@ -63,8 +63,8 @@ class TestSchedule(unittest.TestCase): self.assertEqual(i[0], 11) time.sleep(3) self.assertEqual(i[0], 11) - - + + # vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78: diff --git a/test/test_structures.py b/test/test_structures.py index e72baf8c1..f5432e436 100644 --- a/test/test_structures.py +++ b/test/test_structures.py @@ -183,7 +183,7 @@ class RingBufferTestCase(unittest.TestCase): b = RingBuffer(100, range(10)) b1 = RingBuffer(10, range(10)) self.failIf(b == b1) - + def testIter(self): b = RingBuffer(3, range(3)) L = [] @@ -214,7 +214,7 @@ class QueueTest(unittest.TestCase): self.assertRaises(IndexError, q.__getitem__, -(n+1)) self.assertRaises(IndexError, q.__getitem__, n) self.assertEqual(q[3:7], queue([3, 4, 5, 6])) - + def testSetitem(self): q1 = queue() self.assertRaises(IndexError, q1.__setitem__, 0, 0) @@ -224,7 +224,7 @@ class QueueTest(unittest.TestCase): for (i, elt) in enumerate(q2): q2[i] = elt*2 self.assertEqual([x*2 for x in q1], list(q2)) - + def testNonzero(self): q = queue() self.failIf(q, 'queue not zero after initialization') @@ -301,7 +301,7 @@ class QueueTest(unittest.TestCase): self.assertEqual(q, eval(repr(q)), 'repr doesn\'t eval to same queue') q.enqueue((1,)) self.assertEqual(q, eval(repr(q)), 'repr doesn\'t eval to same queue') - + def testEnqueueDequeue(self): q = queue() self.assertRaises(IndexError, q.dequeue) @@ -360,7 +360,7 @@ class MaxLengthQueueTestCase(unittest.TestCase): q = MaxLengthQueue(3, (1, 2, 3)) self.assertEqual(list(q), [1, 2, 3]) self.assertRaises(TypeError, MaxLengthQueue, 3, 1, 2, 3) - + def testMaxLength(self): q = MaxLengthQueue(3) q.enqueue(1)