From: <bov...@us...> - 2006-10-13 00:12:36
|
Revision: 1265 http://svn.sourceforge.net/pywebsvcs/?rev=1265&view=rev Author: boverhof Date: 2006-10-12 17:12:32 -0700 (Thu, 12 Oct 2006) Log Message: ----------- M doc/c06-tc.tex M doc/c03-except.tex -- updates to typecode chapter, thru Any "Dynamic Typing" M ZSI/__init__.py M ZSI/TC.py M ZSI/parse.py M ZSI/TCcompound.py M CHANGES -- some formatting and changes to booleans for consistency. -- major updates for TC.Any, removed some unused/unimportant code and combined some functions into one. Improved readability and purpose of much of this code. Modified Paths: -------------- trunk/zsi/CHANGES trunk/zsi/ZSI/TC.py trunk/zsi/ZSI/TCcompound.py trunk/zsi/ZSI/__init__.py trunk/zsi/ZSI/parse.py trunk/zsi/doc/c03-except.tex trunk/zsi/doc/c06-tc.tex Modified: trunk/zsi/CHANGES =================================================================== --- trunk/zsi/CHANGES 2006-10-11 22:58:45 UTC (rev 1264) +++ trunk/zsi/CHANGES 2006-10-13 00:12:32 UTC (rev 1265) @@ -5,9 +5,13 @@ - In SoapWriter, put nsdecls on body, not envelope - Record facets (restrictions) in XMLSchema.py <vc...@da...> - Remove Send()'s kwargs out of _args list <ef...@bo...> - - Replace many/most id() with _get_idstr() to hide negative numbers Change for 2.0.0 released xxx: + - Removed "requestclass" keyword argument to Binding.Send + - simplified and retooled Binding/NamedParamBinding and dispatch. + +Change for 2.0.0rc2 released 28-March-2006: + - Replace many/most id() with _get_idstr() to hide negative numbers - Added ZSI.twisted package w/client and server, requires Python 2.4 - If Python >= 2.4, build/install ZSI.twisted package - Add Typecode.typed to control xsi:type data system-wide Modified: trunk/zsi/ZSI/TC.py =================================================================== --- trunk/zsi/ZSI/TC.py 2006-10-11 22:58:45 UTC (rev 1264) +++ trunk/zsi/ZSI/TC.py 2006-10-13 00:12:32 UTC (rev 1265) @@ -196,10 +196,6 @@ return subclass((self.nspname, self.pname)) -#class Nilled: -# '''Just a placeholder for nilled elements. -# ''' -# __init__ = None Nilled = None class TypeCode: @@ -216,11 +212,11 @@ generated if/when needed seriallist -- list of Python types or user-defined classes that this typecode can serialize. + logger -- logger instance for this class. ''' tag = None type = (None,None) - #typechecks = True - typechecks = False + typechecks = True attribute_typecode_dict = None logger = _GetLogger('ZSI.TC.TypeCode') @@ -256,7 +252,6 @@ self.unique = unique self.attrs_aname = attrs_aname self.pyclass = pyclass - #if kw.has_key('default'): self.default = kw['default'] # Need this stuff for rpc/encoded. encoded = kw.get('encoded') @@ -308,7 +303,7 @@ href = _find_href(elt) if not href: if self.minOccurs is 0: return None - raise EvaluateException('Non-optional ' + tag + ' missing', + raise EvaluateException('Required' + tag + ' missing', ps.Backtrace(elt)) return ps.FindLocalHREF(href, elt, 0) @@ -573,7 +568,7 @@ # No content, no HREF, and is NIL... if self.nillable is True: return Nilled - raise EvaluateException('Non-optional string missing', + raise EvaluateException('Requiredstring missing', ps.Backtrace(elt)) if href[0] != '#': @@ -665,23 +660,17 @@ logger = _GetLogger('ZSI.TC.Any') parsemap, serialmap = {}, {} - def __init__(self, pname=None, aslist=0, **kw): - TypeCode.__init__(self, pname, **kw) + def __init__(self, pname=None, aslist=False, minOccurs=0, **kw): + TypeCode.__init__(self, pname, minOccurs=minOccurs, **kw) self.aslist = aslist self.kwargs = {'aslist':aslist} self.kwargs.update(kw) - # If not derived, and optional isn't set, make us optional - # so that None can be parsed. - if self.__class__ == Any and not kw.has_key('optional'): - self.optional = 1 - - #self.asarray = True self.unique = False # input arg v should be a list of tuples (name, value). def listify(self, v): if self.aslist: return [ k for j,k in v ] - else: return dict(v) + return dict(v) def parse_into_dict_or_list(self, elt, ps): c = _child_elements(elt) @@ -698,8 +687,8 @@ if self.nilled(elt, ps): return Nilled for c_elt in c: - # append (name,value) tuple to list - v.append( (str(c_elt.localName), self.__class__(**self.kwargs).parse(c_elt, ps) ) ) + v.append((str(c_elt.localName), self.__class__(**self.kwargs).parse(c_elt, ps))) + return self.listify(v) def parse(self, elt, ps): @@ -708,12 +697,12 @@ if len(_children(elt)) == 0: href = _find_href(elt) if not href: - if self.optional: + if self.minOccurs < 1: if _is_xsd_or_soap_ns(ns): parser = Any.parsemap.get((None,type)) if parser: return parser.parse(elt, ps) return None - raise EvaluateException('Non-optional Any missing', + raise EvaluateException('Required Any missing', ps.Backtrace(elt)) elt = ps.FindLocalHREF(href, elt) (ns,type) = self.checktype(elt, ps) @@ -774,13 +763,13 @@ "xsd:anyType[" + str(len(pyobj)) + "]" ) for o in pyobj: #TODO maybe this should take **self.kwargs... - serializer = getattr(o, 'typecode', self.__class__()) # also used by AnyLax() + serializer = getattr(o, 'typecode', self.__class__()) # also used by _AnyLax() serializer.serialize(array, sw, o, name='element', **kw) else: struct = elt.createAppendElement(ns, n) for o in pyobj: #TODO maybe this should take **self.kwargs... - serializer = getattr(o, 'typecode', self.__class__()) # also used by AnyLax() + serializer = getattr(o, 'typecode', self.__class__()) # also used by _AnyLax() serializer.serialize(struct, sw, o, **kw) return @@ -844,92 +833,6 @@ #serializer.pname = None -def RegisterType(C, clobber=0, *args, **keywords): - instance = apply(C, args, keywords) - for t in C.__dict__.get('parselist', []): - prev = Any.parsemap.get(t) - if prev: - if prev.__class__ == C: continue - if not clobber: - raise TypeError( - str(C) + ' duplicating parse registration for ' + str(t)) - Any.parsemap[t] = instance - for t in C.__dict__.get('seriallist', []): - ti = type(t) - if ti in [ types.TypeType, types.ClassType]: - key = t - elif ti in _stringtypes: - key = (types.ClassType, t) - else: - raise TypeError(str(t) + ' is not a class name') - prev = Any.serialmap.get(key) - if prev: - if prev.__class__ == C: continue - if not clobber: - raise TypeError( - str(C) + ' duplicating serial registration for ' + str(t)) - Any.serialmap[key] = instance - -def _DynamicImport(moduleName, className): - ''' - Utility function for RegisterTypeWithSchemaAndClass - ''' - mod = __import__(moduleName) - components = moduleName.split('.') - for comp in components[1:]: - mod = getattr(mod, comp) - return getattr(mod, className) - -def _RegisterTypeWithSchemaAndClass(importedSchemaTypes, schemaTypeName, classModuleName, className, generatedClassSuffix="_"): - ''' - Used by RegisterGeneratedTypesWithMapping. - Helps register classes so they can be serialized and parsed as "any". - Register a type by providing its schema and class. This allows - Any and AnyType to reconstruct objects made up of your own classes. - Note: The class module should be able to be imported (by being in your - pythonpath). Your classes __init__ functions shoud have default arguments - for all extra parameters. - Example of use: - import SchemaToPyTypeMap # Mapping written by you. Also used with wsdl2py -m - # mapping = {"SomeDescription" : ("Descriptions", "SomeDescription"), - # # schemaTypeName : moduleName , className - # The module on the next line is generated by wsdl2py - from EchoServer_services_types import urn_ZSI_examples as ExampleTypes - - for key,value in SchemaToPyTypeMap.mapping.items(): - ZSI.TC.RegisterTypeWithSchemaAndClass(importedSchemaTypes = ExampleTypes, schemaTypeName=key, classModuleName=value[0], className=value[1]) - - - ''' - # Doing this: (schemaTypeName="ExampleTypes", classModuleName="Description", - # className="SomeDescription") - # sd_instance = ExampleTypes.SomeDescription_(pname="SomeDescription") - # Any.serialmap[Descriptions.SomeDescription] = sd_instance - # Any.parsemap[(None,'SomeDescription')] = sd_instance - classDef = _DynamicImport(classModuleName, className) - interfaceDef = getattr(importedSchemaTypes, schemaTypeName + generatedClassSuffix) - - instance = interfaceDef(pname=className) - Any.serialmap[classDef] = instance - Any.parsemap[(None,schemaTypeName)] = instance - -def RegisterGeneratedTypesWithMapping(generatedTypes, mapping, generatedClassSuffix="_"): - """ - Helps register your classes so they can be serialized and parsed as "any". - generatedTypes is a class containing typecode classes generated by zsi. - mapping is a dictionary that maps {schemaTypeName : moduleName, className} - and is also used with wsdl2py -m - - Example of use: - import SchemaToPyTypeMap # See RegisterTypeWithSchemaAndClass for description - # The module on the next line is generated by wsdl2py and contains generated typecodes. - from EchoServer_services_types import urn_ZSI_examples as ExampleTypes - ZSI.TC.RegisterGeneratedTypesWithMapping(generatedTypes = ExampleTypes, mapping=SchemaToPyTypeMap.mapping) - """ - for key,value in mapping.items(): - _RegisterTypeWithSchemaAndClass(importedSchemaTypes = generatedTypes, schemaTypeName=key, classModuleName=value[0], className=value[1], generatedClassSuffix=generatedClassSuffix) - - class String(SimpleType): '''A string type. ''' @@ -1507,126 +1410,8 @@ xmlelt.setAttributeNS(SOAP.ENC, 'encodingStyle', '""') Canonicalize(pyobj, sw, unsuppressedPrefixes=unsuppressedPrefixes, comments=self.comments) + -# Base class for AnyStrict and AnyLax -class AnyConcrete(Any): - ''' Base class for handling unspecified types when using concrete schemas. - ''' - logger = _GetLogger('ZSI.TC.AnyConcrete') - def __init__(self, pname=None, aslist=False, **kw): - TypeCode.__init__(self, pname, **kw) - self.aslist = aslist - self.asarray = False # don't use arrayType - self.unique = True # don't print id - self.optional = kw.get('optional', True) # Any constructor is not called - - -class AnyStrict(AnyConcrete): - ''' Handles an unspecified types when using a concrete schemas and - processContents = "strict". - ''' - logger = _GetLogger('ZSI.TC.AnyStrict') - - def __init__(self, pname=None, aslist=False, **kw): - AnyConcrete.__init__(self, pname=pname, aslist=aslist, **kw) - - def serialize(self, elt, sw, pyobj, name=None, **kw): - tc = type(pyobj) - if tc == types.DictType and not self.aslist: - raise EvaluateException, 'Serializing dictionaries not implemented when processContents=\"strict\". Try as a list or use processContents=\"lax\".' - else: - - AnyConcrete.serialize(self, elt=elt,sw=sw,pyobj=pyobj,name=name, **kw) - -class AnyLax(AnyConcrete): - ''' Handles unspecified types when using a concrete schemas and - processContents = "lax". - ''' - logger = _GetLogger('ZSI.TC.AnyLax') - - def __init__(self, pname=None, aslist=False, **kw): - AnyConcrete.__init__(self, pname=pname, aslist=aslist, **kw) - - def parse_into_dict_or_list(self, elt, ps): - c = _child_elements(elt) - count = len(c) - v = [] - if count == 0: - href = _find_href(elt) - if not href: return {} - elt = ps.FindLocalHREF(href, elt) - self.checktype(elt, ps) - c = _child_elements(elt) - count = len(c) - if count == 0: return self.listify([]) - if self.nilled(elt, ps): return Nilled - - # group consecutive elements with the same name together - # We treat consecutive elements with the same name as lists. - groupedElements = [] # tuples of (name, elementList) - previousName = "" - currentElementList = None - for ce in _child_elements(elt): - name = ce.localName - if (name != previousName): # new name, so new group - if currentElementList != None: # store previous group if there is one - groupedElements.append( (previousName, currentElementList) ) - currentElementList = list() - currentElementList.append(ce) # append to list - previousName = name - # add the last group if necessary - if currentElementList != None: # store previous group if there is one - groupedElements.append( (previousName, currentElementList) ) - - # parse the groups of names - if len(groupedElements) < 1: # should return earlier - return None - # return a list if there is one name and multiple data - elif (len(groupedElements) == 1) and (len(groupedElements[0][0]) > 1): - self.aslist = 0 - # else return a dictionary - - for name,eltList in groupedElements: - lst = [] - for elt in eltList: - #aslist = self.aslist - lst.append( self.parse(elt, ps) ) - #self.aslist = aslist # restore the aslist setting - if len(lst) > 1: # consecutive elements with the same name means a list - v.append( (name, lst) ) - elif len(lst) == 1: - v.append( (name, lst[0]) ) - - return self.listify(v) - - def checkname(self, elt, ps): - '''See if the name and type of the "elt" element is what we're - looking for. Return the element's type. - Since this is AnyLax, it's ok if names don't resolve. - ''' - - parselist,errorlist = self.get_parse_and_errorlist() - ns, name = _get_element_nsuri_name(elt) - if ns == SOAP.ENC: - # Element is in SOAP namespace, so the name is a type. - if parselist and \ - (None, name) not in parselist and (ns, name) not in parselist: - raise EvaluateException( - 'Element mismatch (got %s wanted %s) (SOAP encoding namespace)' % \ - (name, errorlist), ps.Backtrace(elt)) - return (ns, name) - - # Not a type, check name matches. - if self.nspname and ns != self.nspname: - raise EvaluateException('Element NS mismatch (got %s wanted %s)' % \ - (ns, self.nspname), ps.Backtrace(elt)) - - #if self.pname and name != self.pname: - # this is ok since names don't need to be resolved with AnyLax - - return self.checktype(elt, ps) - - class AnyType(TypeCode): """XML Schema xsi:anyType type definition wildCard. class variables: @@ -1641,7 +1426,8 @@ def __init__(self, pname=None, namespaces=['#all'], minOccurs=1, maxOccurs=1, strip=1, **kw): - TypeCode.__init__(self, pname=pname, minOccurs=minOccurs, maxOccurs=maxOccurs, **kw) + TypeCode.__init__(self, pname=pname, minOccurs=minOccurs, + maxOccurs=maxOccurs, **kw) self.namespaces = namespaces def get_formatted_content(self, pyobj): @@ -1664,29 +1450,32 @@ def serialize(self, elt, sw, pyobj, **kw): nsuri,typeName = _get_xsitype(pyobj) if self.all not in self.namespaces and nsuri not in self.namespaces: - raise EvaluateException, '<anyType> unsupported use of namespaces %s' %self.namespaces - what = pyobj - if hasattr(what, 'typecode'): - what = pyobj.typecode - elif not isinstance(what, TypeCode): + raise EvaluateException( + '<anyType> unsupported use of namespaces "%s"' %self.namespaces) + + what = getattr(pyobj, 'typecode', None) + if what is None: # TODO: resolve this, "strict" processing but no # concrete schema makes little sense. - what = AnyStrict(pname=(self.nspname,self.pname), aslist=False) + #what = _AnyStrict(pname=(self.nspname,self.pname)) + what = Any(pname=(self.nspname,self.pname), unique=True, + aslist=False) kw['typed'] = True what.serialize(elt, sw, pyobj, **kw) return # Namespace if element AnyType was namespaced. - if self.nspname != what.nspname: - what.nspname = self.nspname +# if self.nspname != what.nspname: +# what.nspname = self.nspname +# +# if self.pname != what.pname: +# raise EvaluateException, \ +# 'element name of typecode(%s) must match element name of AnyType(%s)' \ +# %(what.pname,self.pname) - if self.pname != what.pname: - raise EvaluateException, \ - 'element name of typecode(%s) must match element name of AnyType(%s)' \ - %(what.pname,self.pname) + what.serialize(elt, sw, pyobj, + name=(self.nspname or what.nspname, self.pname or what.pname), **kw) - what.serialize(elt, sw, pyobj, **kw) - def parse(self, elt, ps): #element name must be declared .. nspname,pname = _get_element_nsuri_name(elt) @@ -1700,7 +1489,7 @@ pyclass = _get_type_definition(namespaceURI, typeName) if not pyclass: if _is_xsd_or_soap_ns(namespaceURI): - pyclass = AnyStrict + pyclass = _AnyStrict elif (str(namespaceURI).lower()==str(Apache.Map.type[0]).lower())\ and (str(typeName).lower() ==str(Apache.Map.type[1]).lower()): pyclass = Apache.Map @@ -1720,20 +1509,18 @@ tag -- global element declaration """ tag = (SCHEMA.XSD3, 'any') - + logger = _GetLogger('ZSI.TC.AnyElement') + def __init__(self, namespaces=['#all'],pname=None, - minOccurs=1, maxOccurs=1, strip=1, processContents='strict',**kw): + minOccurs=1, maxOccurs=1, strip=1, processContents='strict', + **kw): + + if processContents not in ('lax', 'skip', 'strict'): + raise ValueError('processContents(%s) must be lax, skip, or strict') + self.processContents = processContents AnyType.__init__(self, namespaces=namespaces,pname=pname, minOccurs=minOccurs, maxOccurs=maxOccurs, strip=strip, **kw) - - def getProcessContents(self, processContents): - return self._processContents - def setProcessContents(self, processContents): - if processContents not in ('lax', 'skip', 'strict'): - raise EvaluateException, '<any> processContents(%s) is not understood.' %processContents - self._processContents = processContents - processContents = property(getProcessContents, setProcessContents, None, '<any> processContents') def serialize(self, elt, sw, pyobj, **kw): '''Must provice typecode to AnyElement for serialization, else @@ -1741,33 +1528,26 @@ based on the data type of pyobj w/o reference to XML schema instance. ''' - what = None - if hasattr(pyobj, 'typecode'): - what = getattr(pyobj, 'typecode') - #May want to look thru containers and try to find a match - elif type(pyobj) == types.InstanceType: + if isinstance(pyobj, TypeCode): + raise TypeError, 'pyobj is a typecode instance.' + + what = getattr(pyobj, 'typecode', None) + if what is not None and type(pyobj) is types.InstanceType: tc = pyobj.__class__ what = Any.serialmap.get(tc) if not what: tc = (types.ClassType, pyobj.__class__.__name__) what = Any.serialmap.get(tc) - if what == None: - if isinstance(pyobj, TypeCode): - raise EvaluateException, '<any> pyobj is a typecode instance.' - #elif type(pyobj) in (list,tuple,dict): - # raise EvaluateException, '<any> can\'t serialize pyobj %s' \ - # %type(pyobj) - elif kw.has_key('pname'): - if self.processContents=='lax': - what = AnyLax(pname=(kw.get('nspname'),kw['pname'])) - else: - what = AnyStrict(pname=(kw.get('nspname'),kw['pname'])) + + # failed to find a registered type for class + if what is None: + #TODO: seems incomplete. what about facets. + if self.processContents == 'strict': + what = _AnyStrict(pname=(self.nspname,self.pname)) else: - if self.processContents=='lax': - what = AnyLax() - else: - what = AnyStrict() - self.logger.debug('AnyElement.serialize with %s', what.__class__.__name__) + what = _AnyLax(pname=(self.nspname,self.pname)) + + self.logger.debug('serialize with %s', what.__class__.__name__) what.serialize(elt, sw, pyobj, **kw) def parse(self, elt, ps): @@ -1778,54 +1558,52 @@ not found return DOM node. 3) if 'strict' get declaration, or raise. ''' + skip = self.processContents == 'skip' nspname,pname = _get_element_nsuri_name(elt) what = _get_global_element_declaration(nspname, pname) - if what is None: - # if self.processContents == 'strict': raise - # Allow use of "<any>" element declarations w/ local element declarations - prefix, typeName = SplitQName(_find_type(elt)) - if typeName: - namespaceURI = _resolve_prefix(elt, prefix or 'xmlns') - # First look thru user defined namespaces, if don't find - # look for 'primitives'. - pyclass = _get_type_definition(namespaceURI, typeName) - if pyclass is None: - if not _is_xsd_or_soap_ns(namespaceURI): - raise EvaluateException('<any> cant find typecode for type (%s,%s)' %( - namespaceURI,typeName), ps.Backtrace(elt)) - if self.getProcessContents=='lax': - pyclass = AnyLax - else: - pyclass = AnyStrict - - what = pyclass(pname=(nspname,pname)) - pyobj = what.parse(elt, ps) - try: - pyobj.typecode = what - except AttributeError, ex: - # Assume this means builtin type. - pyobj = Wrap(pyobj, what) - else: - if self.processContents == 'lax': - what = AnyLax(pname=(nspname,pname)) - else: # processContents == 'skip' - # All else fails, not typed, attempt to use XML, String - what = XML(pname=(nspname,pname), wrapped=False) - try: - pyobj = what.parse(elt, ps) - except EvaluateException, ex: - self.logger.error("Give up, parse (%s,%s) as a String", what.nspname, what.pname) - # Try returning "elt" - what = String(pname=(nspname,pname)) - pyobj = Wrap(what.parse(elt, ps), what) - else: + if not skip and what is not None: pyobj = what.parse(elt, ps) try: pyobj.typecode = what except AttributeError, ex: # Assume this means builtin type. pyobj = _GetPyobjWrapper.Wrap(pyobj, what) + return pyobj + + # Allow use of "<any>" element declarations w/ local + # element declarations + prefix, typeName = SplitQName(_find_type(elt)) + if not skip and typeName: + namespaceURI = _resolve_prefix(elt, prefix or 'xmlns') + # First look thru user defined namespaces, if don't find + # look for 'primitives'. + pyclass = _get_type_definition(namespaceURI, typeName) or Any + what = pyclass(pname=(nspname,pname)) + pyobj = what.parse(elt, ps) + try: + pyobj.typecode = what + except AttributeError, ex: + # Assume this means builtin type. + pyobj = Wrap(pyobj, what) + + what.typed = True + return pyobj + if skip: + what = XML(pname=(nspname,pname), wrapped=False) + elif self.processContents == 'lax': + what = _AnyLax(pname=(nspname,pname)) + else: + what = _AnyStrict(pname=(nspname,pname)) + + try: + pyobj = what.parse(elt, ps) + except EvaluateException, ex: + self.logger.error("Give up, parse (%s,%s) as a String", + what.nspname, what.pname) + what = String(pname=(nspname,pname), typed=False) + pyobj = Wrap(what.parse(elt, ps), what) + return pyobj @@ -1985,7 +1763,7 @@ # No content, no HREF, and is NIL... if self.nillable is True: return Nilled - raise EvaluateException('Non-optional string missing', + raise EvaluateException('Required string missing', ps.Backtrace(elt)) if href[0] != '#': return ps.ResolveHREF(href, self) @@ -2022,6 +1800,115 @@ el.createAppendTextNode(textNode) +class _AnyStrict(Any): + ''' Handles an unspecified types when using a concrete schemas and + processContents = "strict". + ''' + #WARNING: unstable + logger = _GetLogger('ZSI.TC._AnyStrict') + + def __init__(self, pname=None, aslist=False, **kw): + TypeCode.__init__(self, pname=pname, **kw) + self.aslist = aslist + self.unique = True + + def serialize(self, elt, sw, pyobj, name=None, **kw): + if not (type(pyobj) is dict and not self.aslist): + Any.serialize(self, elt=elt,sw=sw,pyobj=pyobj,name=name, **kw) + + raise EvaluateException( + 'Serializing dictionaries not implemented when processContents=\"strict\".' + + 'Try as a list or use processContents=\"lax\".' + ) + + +class _AnyLax(Any): + ''' Handles unspecified types when using a concrete schemas and + processContents = "lax". + ''' + logger = _GetLogger('ZSI.TC._AnyLax') + + def __init__(self, pname=None, aslist=False, **kw): + TypeCode.__init__(self, pname=pname, **kw) + self.aslist = aslist + self.unique = True + + def parse_into_dict_or_list(self, elt, ps): + c = _child_elements(elt) + count = len(c) + v = [] + if count == 0: + href = _find_href(elt) + if not href: return {} + elt = ps.FindLocalHREF(href, elt) + self.checktype(elt, ps) + c = _child_elements(elt) + count = len(c) + if count == 0: return self.listify([]) + if self.nilled(elt, ps): return Nilled + + # group consecutive elements with the same name together + # We treat consecutive elements with the same name as lists. + groupedElements = [] # tuples of (name, elementList) + previousName = "" + currentElementList = None + for ce in _child_elements(elt): + name = ce.localName + if (name != previousName): # new name, so new group + if currentElementList != None: # store previous group if there is one + groupedElements.append( (previousName, currentElementList) ) + currentElementList = list() + currentElementList.append(ce) # append to list + previousName = name + # add the last group if necessary + if currentElementList != None: # store previous group if there is one + groupedElements.append( (previousName, currentElementList) ) + + # parse the groups of names + if len(groupedElements) < 1: # should return earlier + return None + # return a list if there is one name and multiple data + elif (len(groupedElements) == 1) and (len(groupedElements[0][0]) > 1): + self.aslist = False + # else return a dictionary + + for name,eltList in groupedElements: + lst = [] + for elt in eltList: + #aslist = self.aslist + lst.append( self.parse(elt, ps) ) + #self.aslist = aslist # restore the aslist setting + if len(lst) > 1: # consecutive elements with the same name means a list + v.append( (name, lst) ) + elif len(lst) == 1: + v.append( (name, lst[0]) ) + + return self.listify(v) + + def checkname(self, elt, ps): + '''See if the name and type of the "elt" element is what we're + looking for. Return the element's type. + Since this is _AnyLax, it's ok if names don't resolve. + ''' + + parselist,errorlist = self.get_parse_and_errorlist() + ns, name = _get_element_nsuri_name(elt) + if ns == SOAP.ENC: + if parselist and \ + (None, name) not in parselist and (ns, name) not in parselist: + raise EvaluateException( + 'Element mismatch (got %s wanted %s) (SOAP encoding namespace)' % \ + (name, errorlist), ps.Backtrace(elt)) + return (ns, name) + + # Not a type, check name matches. + if self.nspname and ns != self.nspname: + raise EvaluateException('Element NS mismatch (got %s wanted %s)' % \ + (ns, self.nspname), ps.Backtrace(elt)) + + return self.checktype(elt, ps) + + class _Mirage: '''Used with SchemaInstanceType for lazy evaluation, eval during serialize or parse as needed. Mirage is callable, TypeCodes are not. When called it returns the @@ -2042,16 +1929,6 @@ if issubclass(self.klass, ElementDeclaration): msg = "<Mirage id=%s, GED %s>" return msg %(id(self), self.klass) - -# def __getattr__(self, attr): -# '''try to look just like the typecode. -# ''' -# if self.__reveal is False: -# if self.__cache is None: self.__call__() -# return getattr(self.__cache, attr) -# -# raise AttributeError('Missing attribute "%s", mirage must hide item before it can be revealed' % -# attr) def _hide_type(self, pname, aname, minOccurs=0, maxOccurs=1, nillable=False, **kw): @@ -2106,21 +1983,36 @@ and thus can be serialized. ''' types_dict = {} - for builtin_type in [int,float,str,tuple,list,unicode]: - class Wrapper(builtin_type): pass - types_dict[builtin_type] = Wrapper - + + def RegisterBuiltin(cls, arg): + '''register a builtin, create a new wrapper. + ''' + if arg in cls.types_dict: + raise RuntimeError, '%s already registered' %arg + class _Wrapper(arg): + 'Wrapper for builtin %s\n%s' %(arg, cls.__doc__) + _Wrapper.__name__ = '_%sWrapper' %arg + cls.types_dict[arg] = _Wrapper + RegisterBuiltin = classmethod(RegisterBuiltin) + def RegisterAnyElement(cls): + '''clobber all existing entries in Any serialmap, + replace with Wrapper classes. + ''' for k,v in cls.types_dict.items(): what = Any.serialmap.get(k) if what is None: continue - if v in what.__class__.seriallist: - continue + if v in what.__class__.seriallist: continue what.__class__.seriallist.append(v) RegisterType(what.__class__, clobber=1) RegisterAnyElement = classmethod(RegisterAnyElement) def Wrap(cls, pyobj, what): + '''return a wrapper for pyobj, with typecode attribute set. + Parameters: + pyobj -- instance of builtin type (immutable) + what -- typecode describing the data + ''' d = cls.types_dict if type(pyobj) is bool: pyclass = d[int] @@ -2128,7 +2020,8 @@ pyclass = d[type(pyobj)] else: raise TypeError,\ - 'Expecting a built-in type in %s (got %s).' %(d.keys(),type(pyobj)) + 'Expecting a built-in type in %s (got %s).' %( + d.keys(),type(pyobj)) newobj = pyclass(pyobj) newobj.typecode = what @@ -2137,12 +2030,61 @@ def Wrap(pyobj, what): + '''Wrap immutable instance so a typecode can be + set, making it self-describing ie. serializable. + ''' return _GetPyobjWrapper.Wrap(pyobj, what) + +def RegisterBuiltin(arg): + '''Add a builtin to be registered, and register it + with the Any typecode. + ''' + _GetPyobjWrapper.RegisterBuiltin(arg) + _GetPyobjWrapper.RegisterAnyElement() + + def RegisterAnyElement(): + '''register all Wrapper classes with the Any typecode. + This allows instances returned by Any to be self-describing. + ie. serializable. AnyElement falls back on Any to parse + anything it doesn't understand. + ''' return _GetPyobjWrapper.RegisterAnyElement() +def RegisterType(C, clobber=0, *args, **keywords): + instance = apply(C, args, keywords) + for t in C.__dict__.get('parselist', []): + prev = Any.parsemap.get(t) + if prev: + if prev.__class__ == C: continue + if not clobber: + raise TypeError( + str(C) + ' duplicating parse registration for ' + str(t)) + Any.parsemap[t] = instance + for t in C.__dict__.get('seriallist', []): + ti = type(t) + if ti in [ types.TypeType, types.ClassType]: + key = t + elif ti in _stringtypes: + key = (types.ClassType, t) + else: + raise TypeError(str(t) + ' is not a class name') + prev = Any.serialmap.get(key) + if prev: + if prev.__class__ == C: continue + if not clobber: + raise TypeError( + str(C) + ' duplicating serial registration for ' + str(t)) + Any.serialmap[key] = instance + + +# Load up Wrappers for builtin types +for i in [int,float,str,tuple,list,unicode]: + _GetPyobjWrapper.RegisterBuiltin(i) + + from TCnumbers import * from TCtimes import * from TCcompound import * Modified: trunk/zsi/ZSI/TCcompound.py =================================================================== --- trunk/zsi/ZSI/TCcompound.py 2006-10-11 22:58:45 UTC (rev 1264) +++ trunk/zsi/ZSI/TCcompound.py 2006-10-13 00:12:32 UTC (rev 1265) @@ -5,7 +5,8 @@ from ZSI import _copyright, _children, _child_elements, \ _inttypes, _stringtypes, _seqtypes, _find_arraytype, _find_href, \ - _find_type, _find_xmlns_prefix, _get_idstr, EvaluateException + _find_type, _find_xmlns_prefix, _get_idstr, EvaluateException, \ + ParseException from ZSI.TC import _get_element_nsuri_name, \ _get_substitute_element, _get_type_definition, _get_xsitype, \ TypeCode, Any, AnyElement, AnyType, ElementDeclaration, TypeDefinition, \ @@ -25,6 +26,8 @@ '''Check a list of typecodes for compliance with Struct requirements.''' for o in ofwhat: + if callable(o): #skip if _Mirage + continue if not isinstance(o, TypeCode): raise TypeError( tcname + ' ofwhat outside the TypeCode hierarchy, ' + @@ -408,11 +411,13 @@ if debug and what is not whatTC: self.logger.debug('substitute derived type: %s' % what.__class__) - try: - what.serialize(elem, sw, v2, **kw) - except Exception, e: - raise EvaluateException('Serializing %s.%s, %s %s' % - (n, whatTC.aname or '?', e.__class__.__name__, str(e))) + + what.serialize(elem, sw, v2, **kw) +# try: +# what.serialize(elem, sw, v2, **kw) +# except Exception, e: +# raise EvaluateException('Serializing %s.%s, %s %s' % +# (n, whatTC.aname or '?', e.__class__.__name__, str(e))) if occurs < whatTC.minOccurs: raise EvaluateException(\ @@ -426,13 +431,15 @@ if debug and what is not whatTC: self.logger.debug('substitute derived type: %s' % what.__class__) - - try: - what.serialize(elem, sw, v, **kw) - except Exception, e: - raise EvaluateException('Serializing %s.%s, %s %s' % - (n, whatTC.aname or '?', e.__class__.__name__, str(e)), - sw.Backtrace(elt)) + what.serialize(elem, sw, v, **kw) +# try: +# what.serialize(elem, sw, v, **kw) +# except (ParseException, EvaluateException), e: +# raise +# except Exception, e: +# raise EvaluateException('Serializing %s.%s, %s %s' % +# (n, whatTC.aname or '?', e.__class__.__name__, str(e)), +# sw.Backtrace(elt)) continue raise EvaluateException('Got None for nillable(%s), minOccurs(%d) element (%s,%s), %s' % Modified: trunk/zsi/ZSI/__init__.py =================================================================== --- trunk/zsi/ZSI/__init__.py 2006-10-11 22:58:45 UTC (rev 1264) +++ trunk/zsi/ZSI/__init__.py 2006-10-13 00:12:32 UTC (rev 1265) @@ -177,8 +177,8 @@ try: from xml.dom import EMPTY_NAMESPACE _empty_nsuri_list = [ EMPTY_NAMESPACE ] - if '' not in _empty_nsuri_list: __empty_nsuri_list.append('') - if None not in _empty_nsuri_list: __empty_nsuri_list.append(None) + #if '' not in _empty_nsuri_list: __empty_nsuri_list.append('') + #if None not in _empty_nsuri_list: __empty_nsuri_list.append(None) except: _empty_nsuri_list = [ None, '' ] def _find_attr(E, attr): @@ -226,7 +226,7 @@ _find_xmlns_prefix = lambda E, attr: E.getAttributeNS(_XMLNS.BASE, attr) _find_default_namespace = lambda E: E.getAttributeNS(_XMLNS.BASE, None) -_textprotect = lambda s: s.replace('&', '&').replace('<', '<') +#_textprotect = lambda s: s.replace('&', '&').replace('<', '<') _get_element_nsuri_name = lambda E: (E.namespaceURI, E.localName) Modified: trunk/zsi/ZSI/parse.py =================================================================== --- trunk/zsi/ZSI/parse.py 2006-10-11 22:58:45 UTC (rev 1264) +++ trunk/zsi/ZSI/parse.py 2006-10-13 00:12:32 UTC (rev 1265) @@ -34,15 +34,15 @@ ''' defaultReaderClass = None - def __init__(self, input, readerclass=None, keepdom=0, - trailers=0, resolver=None, envelope=True, **kw): + def __init__(self, input, readerclass=None, keepdom=False, + trailers=False, resolver=None, envelope=True, **kw): '''Initialize. Keyword arguments: trailers -- allow trailer elments (default is zero) resolver -- function (bound method) to resolve URI's readerclass -- factory class to create a reader keepdom -- do not release the DOM - envelope -- do not loop for a SOAP envelope. + envelope -- look for a SOAP envelope. ''' self.readerclass = readerclass Modified: trunk/zsi/doc/c03-except.tex =================================================================== --- trunk/zsi/doc/c03-except.tex 2006-10-11 22:58:45 UTC (rev 1264) +++ trunk/zsi/doc/c03-except.tex 2006-10-13 00:12:32 UTC (rev 1265) @@ -1,12 +1,13 @@ \chapter{Exceptions} \begin{excdesc}{ZSIException} -Base class for all ZSI Exceptions. +Base class for all ZSI Exceptions, it is a subtype of the Python +\exception{Exception} class. \end{excdesc} \begin{excdesc}{ParseException} \ZSI{} can raise this exception while creating a \class{ParsedSoap} object. -It is a subtype of Python's \exception{Exception} class. +It is a subtype of the \exception{ZSIException} class. The string form of a \exception{ParseException} object consists of a line of human-readable text. If the backtrace is available, it will be concatenated as a second line. Modified: trunk/zsi/doc/c06-tc.tex =================================================================== --- trunk/zsi/doc/c06-tc.tex 2006-10-11 22:58:45 UTC (rev 1264) +++ trunk/zsi/doc/c06-tc.tex 2006-10-13 00:12:32 UTC (rev 1265) @@ -10,68 +10,55 @@ All typecodes classes have the prefix \code{TC.}, to avoid name clashes. \ZSI{} provides fine-grain control over the names used when parsing and -serializing XML into local Python objects, through the use of three -attributes: the \code{pname}, the \code{aname}, and the \code{oname} -(in approximate order of importance). They specify the name expected on -the XML element being parsed, the name to use for the analogous attribute -in the local Python object, and the name to use for the output element -when serializing. +serializing XML into local Python objects, through the use of two +attributes: the \code{pname}, the \code{aname}. The \code{pname} specifies the +name expected on the XML element being parsed and the name to use for the output element +when serializing. The \code{aname} is the name to use for the analogous +attribute in the local Python object. The \code{pname} is the parameter name. It specifies the incoming XML element name and the default values for the Python attribute -and serialized names. All typecodes take name argument, known as -\code{name}, for the \code{pname}. This name can be specified as -either a list or a string. When specified as a list, it must have two -elements which are interpreted as a ``(namespace-URI, localname)'' pair. +and serialized names. All typecodes take the \code{pname} argument. This name can be +specified as either a list or a string. When specified as a list, it must have +two elements which are interpreted as a ``(namespace-URI, localname)'' pair. If specified this way, both the namespace and the local element name must match for the parse to succeed. For the Python attribute, and -when generating output, only the ``localname'' is used. (Because the -output name is not namespace-qualified, it may be necessary to set the -default namespace, such as through the \code{nsdict} parameter of the -\class{SoapWriter} class. When the name is specified as a string, it -can be either a simple XML name (such as ``foo''), or a colon-separated -XML qualified name (such as ``tns:foo''). If a qualified name is used, -the namespace prefix is ignore on input and for the Python attribute, -but the full qualified name is used for output; this \emph{requires} -the namespace prefix to be specified. +when generating output, only the ``localname'' is used. If a namespace-URI is +specified then the full qualified name is used for output, and it is required +for input; this \emph{requires} the namespace prefix to be specified. The \code{aname} is the attribute name. This parameter overrides -any value implied by the \code{pname}. Typecodes nested in a the -\class{TC.Struct} or \class{TC.Choice} can use this parameter to specify +any value implied by the \code{pname}. Typecodes nested in a \class{TC.Struct} +or \class{TC.ComplexType} can use this parameter to specify the tag, dictionary key, or instance attribute to set. -The final name, \code{oname}, specifies the name to use for the XML element -when serializing. This is most useful when using the same typecode for -both parsing and serializing operations. It can be any string, and is -output directly; a name like ``tns:foo'' implies that the \code{nsdict} -parameter to the \class{SoapWriter} construct should have an entry for -``tns,'' otherwise the resulting output will not be well-formed XML. +The \code{nsdict} parameter to the \class{SoapWriter} construct can be used to +specify prefix to namespace-URI mappings, these are otherwise handled automatically. -\begin{classdesc}{TypeCode}{name, **keywords} -The \code{name} parameter is the name of the object; this is only -required when a typecode appears within a \class{TC.Struct} as it defines -the attribute name used to hold the data, or within a \class{TC.Choice} -as it determines the data type. -(Since SOAP RPC models transfer as structures, this essentially means that -a the \code{name} parameter can never be \code{None}.) +\begin{classdesc}{TypeCode}{**keywords} +The \code{pname} The following keyword arguments may be used: \begin{tableiii}{l|c|p{30em}}{textrm}{Keyword}{Default}{Description} -\lineiii{\code{aname}}{\-}{See name discussion above.} -\lineiii{\code{default}}{n/a}{Value if the element is not specified.} -\lineiii{\code{optional}}{\code{0}}{The element is optional; see below.} -\lineiii{\code{oname}}{\-}{See name discussion above.} -\lineiii{\code{repeatable}}{\code{0}}{If multiple instances of this -occur in a \class{TC.Struct}, collect the values into a list. -\versionadded{1.2}} -\lineiii{\code{typed}}{\code{1}}{Output type information (in the -\code{xsi:type} attribute) when serializing. By special dispensation, -typecodes within a \class{TC.Struct} object inherit this from the -container.} +\lineiii{\code{pname}}{\code{None}}{parameter name of the object} +\lineiii{\code{aname}}{\code{None}}{attribute name of the object} +\lineiii{\code{minOccurs}}{\code{1}}{schema facet minimum occurances} +\lineiii{\code{maxOccurs}}{\code{1}}{schema facet maximum occurances} +\lineiii{\code{nillable}}{\code{False}}{schema facet is this nillable (\code{xsi:nil="true"})} +\lineiii{\code{typed}}{\code{True}}{Output type information (in the \code{xsi:type} +attribute) when serializing. By special dispensation, typecodes within a +\class{TC.Struct} object inherit this from the container.} \lineiii{\code{unique}}{\code{0}}{If true, the object is unique and will never be ``aliased'' with another object, so the \code{id} attribute need not be output.} + +\lineiii{\code{pyclass}}{\code{None}}{when parsing data, instances of this class +can be created to store the data. Default behavior is reflective of specific +TypeCode classes.} +\lineiii{\code{attrs_aname}}{\code{'_attrs'}}{attribute name of the object where +attribute values are stored. Used for serialization and parsing.} + \end{tableiii} Optional elements are those which do not have to be an incoming @@ -91,17 +78,26 @@ rigorous type-checking on their parameters. \end{memberdesc} -\begin{memberdesc}{typed} +\begin{memberdesc}{tag} This is a class attribute. -This sets the default value for whether or not typecodes output -typing (\code{xsi:type} attribute) information. -The default is none-zero. +Specifies the global element declaration this typecode represents, the value is +a \samp{(namespace, name)} tuple. \end{memberdesc} +\begin{memberdesc}{type} +This is a class attribute. +Specifies the global type definition this typecode represents, the value is +a \samp{(namespace, name)} tuple. +\end{memberdesc} + +\begin{memberdesc}{logger} +This is a class attribute. +logger instance for this class. +\end{memberdesc} + The following methods are useful for defining new typecode classes; -see the section on dynamic typing for more details. -In all of the following, the \code{ps} parameter is a \class{ParsedSoap} -object. +see the section on dynamic typing for more details. In all of the following, +the \code{ps} parameter is a \class{ParsedSoap} object. \begin{methoddesc}{checkname}{elt, ps} Checks if the name and type of the element \code{elt} are @@ -117,16 +113,19 @@ \end{methoddesc} \begin{methoddesc}{nilled}{elt, ps} -If the element \code{elt} has data, this returns \code{0}. +If the element \code{elt} has data, this returns \code{False}. If it has no data, and the typecode is not optional, an \exception{EvaluateException} is raised; if it is optional, -a \code{1} is returned. +a \code{True} is returned. \end{methoddesc} -\begin{methoddesc}{simple_value}{elt, ps} +\begin{methoddesc}{simple_value}{elt, ps, mixed=False} Returns the text content of the element \code{elt}. If no value is present, or the element has non-text children, an -\exception{EvaluateException} is raised. +\exception{EvaluateException} is raised. If \code{mixed} is \code{False} if +child elements are discovered an \exception{EvaluateException} is raised, else +join all text nodes and return the result. + \end{methoddesc} \section{\class{TC.Any} --- the basis of dynamic typing} @@ -136,9 +135,10 @@ schema. For example, the following are all possible ways of encoding an integer element \code{i} with a value of \code{12}: +\subsection{simple data} -- requires type information \begin{verbatim} <tns:i xsi:type="SOAP-ENC:integer">12</tns:i> -<tns:i xsi:type="xsi:nonNegativeInteger">12</tns:i> +<tns:i xsi:type="xsd:nonNegativeInteger">12</tns:i> <SOAP-ENC:integer>12</SOAP-ENC:integer> <tns:i>12</tns:i> \end{verbatim} @@ -146,19 +146,20 @@ The first three lines are examples of \emph{typed} elements. If \ZSI{} is asked to parse any of the above examples, and a \class{TC.Any} typecode is given, it will properly create a Python -integer for the first three, and raise a \exception{ParseException} +integer for the first three, and raise a \exception{EvaluateException} for the fourth. +\subsection{compound data} -- Struct or Array Compound data, such as a \code{struct}, may also be self-describing: \begin{verbatim} -<tns:foo xsi:type="tns:mytype"> +<tns:foo> <tns:i xsi:type="SOAP-ENC:integer">12</tns:i> <tns:name xsi:type="SOAP-ENC:string">Hello world</tns:name> </tns:foo> \end{verbatim} -If this is parsed with a \class{TC.Any} typecode, either a Python dictionary -or a sequence will be created: +If this is parsed with a \class{TC.Any} typecode, either a Python \code{dict} +is created or if \code{aslist} is True a \code{list}: \begin{verbatim} { 'name': u'Hello world', 'i': 12 } @@ -166,6 +167,7 @@ \end{verbatim} Note that one preserves order, while the other preserves the element names. +\subsection{class description} \begin{classdesc}{Any}{name\optional{, **keywords}} Used for parsing incoming SOAP data (that is typed), and serializing outgoing Python data. @@ -173,7 +175,7 @@ The following keyword arguments may be used: \begin{tableiii}{l|c|p{30em}}{textrm}{Keyword}{Default}{Description} -\lineiii{\code{aslist}}{\code{0}}{If true, then the data is (recursively) +\lineiii{\code{aslist}}{\code{False}}{If true, then the data is (recursively) treated as a list of values. The default is a Python dictionary, which preserves parameter names but loses the ordering. @@ -270,16 +272,6 @@ processed as dynamic types. This may be acceptable in some environments. -\section{Void} - -A SOAP void is a Python \code{None}. - -\begin{classdesc}{Void}{name\optional{, **keywords}} -A \code{Void} is an item without a value. -It is of marginal utility, mainly useful for interoperability tests, and -as an optional item within a \code{Struct}. -\end{classdesc} - \section{Strings} SOAP Strings are Python strings. This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |