From: <bov...@us...> - 2006-10-17 22:53:38
|
Revision: 1268 http://svn.sourceforge.net/pywebsvcs/?rev=1268&view=rev Author: boverhof Date: 2006-10-17 15:53:19 -0700 (Tue, 17 Oct 2006) Log Message: ----------- M test/test_t6.py M test/test_t4.py -- fixed a couple failure conditions M ZSI/dispatch.py M ZSI/fault.py M ZSI/client.py M doc/c10-dispatch.tex M doc/c08-fault.tex -- updates to docs, and moved a little code around and minor comment changes. Modified Paths: -------------- trunk/zsi/ZSI/client.py trunk/zsi/ZSI/dispatch.py trunk/zsi/ZSI/fault.py trunk/zsi/doc/c08-fault.tex trunk/zsi/doc/c10-dispatch.tex trunk/zsi/test/test_t4.py trunk/zsi/test/test_t6.py Modified: trunk/zsi/ZSI/client.py =================================================================== --- trunk/zsi/ZSI/client.py 2006-10-17 19:32:22 UTC (rev 1267) +++ trunk/zsi/ZSI/client.py 2006-10-17 22:53:19 UTC (rev 1268) @@ -67,8 +67,7 @@ class _Binding: '''Object that represents a binding (connection) to a SOAP server. Once the binding is created, various ways of sending and - receiving SOAP messages are available, including a "name overloading" - style. + receiving SOAP messages are available. ''' defaultHttpTransport = httplib.HTTPConnection defaultHttpsTransport = httplib.HTTPSConnection @@ -444,7 +443,9 @@ class Binding(_Binding): - ''' + '''Object that represents a binding (connection) to a SOAP server. + Can be used in the "name overloading" style. + class attr: gettypecode -- funcion that returns typecode from typesmodule, can be set so can use whatever mapping you desire. @@ -522,7 +523,7 @@ class NamedParamBinding(Binding): - '''Like binding, except the argument list for invocation is + '''Like Binding, except the argument list for invocation is named parameters. ''' logger = _GetLogger('ZSI.client.Binding') Modified: trunk/zsi/ZSI/dispatch.py =================================================================== --- trunk/zsi/ZSI/dispatch.py 2006-10-17 19:32:22 UTC (rev 1267) +++ trunk/zsi/ZSI/dispatch.py 2006-10-17 22:53:19 UTC (rev 1268) @@ -181,28 +181,7 @@ def _CGISendFault(f, **kw): _CGISendXML(f.AsSOAP(), 500, **kw) -def AsCGI(nsdict={}, typesmodule=None, rpc=None, modules=None): - '''Dispatch within a CGI script. - ''' - if os.environ.get('REQUEST_METHOD') != 'POST': - _CGISendFault(Fault(Fault.Client, 'Must use POST')) - return - ct = os.environ['CONTENT_TYPE'] - try: - if ct.startswith('multipart/'): - cid = resolvers.MIMEResolver(ct, sys.stdin) - xml = cid.GetSOAPPart() - ps = ParsedSoap(xml, resolver=cid.Resolve) - else: - length = int(os.environ['CONTENT_LENGTH']) - ps = ParsedSoap(sys.stdin.read(length)) - except ParseException, e: - _CGISendFault(FaultFromZSIException(e)) - return - _Dispatch(ps, modules, _CGISendXML, _CGISendFault, nsdict=nsdict, - typesmodule=typesmodule, rpc=rpc) - class SOAPRequestHandler(BaseHTTPRequestHandler): '''SOAP handler. ''' @@ -247,9 +226,9 @@ docstyle=self.server.docstyle, nsdict=self.server.nsdict, typesmodule=self.server.typesmodule, rpc=self.server.rpc) -def AsServer(port=80, modules=None, docstyle=0, nsdict={}, typesmodule=None, - rpc=None, **kw): - address = ('', port) +def AsServer(port=80, modules=None, docstyle=False, nsdict={}, typesmodule=None, + rpc=False, addr=''): + address = (addr, port) httpd = HTTPServer(address, SOAPRequestHandler) httpd.modules = modules httpd.docstyle = docstyle @@ -258,14 +237,34 @@ httpd.rpc = rpc httpd.serve_forever() -def AsHandler(request=None, modules=None, nsdict={}, rpc=None, **kw): +def AsCGI(nsdict={}, typesmodule=None, rpc=False, modules=None): + '''Dispatch within a CGI script. + ''' + if os.environ.get('REQUEST_METHOD') != 'POST': + _CGISendFault(Fault(Fault.Client, 'Must use POST')) + return + ct = os.environ['CONTENT_TYPE'] + try: + if ct.startswith('multipart/'): + cid = resolvers.MIMEResolver(ct, sys.stdin) + xml = cid.GetSOAPPart() + ps = ParsedSoap(xml, resolver=cid.Resolve) + else: + length = int(os.environ['CONTENT_LENGTH']) + ps = ParsedSoap(sys.stdin.read(length)) + except ParseException, e: + _CGISendFault(FaultFromZSIException(e)) + return + _Dispatch(ps, modules, _CGISendXML, _CGISendFault, nsdict=nsdict, + typesmodule=typesmodule, rpc=rpc) + +def AsHandler(request=None, modules=None, **kw): '''Dispatch from within ModPython.''' ps = ParsedSoap(request) kw['request'] = request - _Dispatch(ps, modules, _ModPythonSendXML, _ModPythonSendFault, - nsdict=nsdict, rpc=rpc, **kw) - -def AsJonPy(nsdict={}, typesmodule=None, rpc=None, modules=None, request=None, **kw): + _Dispatch(ps, modules, _ModPythonSendXML, _ModPythonSendFault, **kw) + +def AsJonPy(request=None, modules=None, **kw): '''Dispatch within a jonpy CGI/FastCGI script. ''' @@ -285,8 +284,7 @@ except ParseException, e: _JonPySendFault(FaultFromZSIException(e), **kw) return - _Dispatch(ps, modules, _JonPySendXML, _JonPySendFault, nsdict=nsdict, - typesmodule=typesmodule, rpc=rpc, **kw) + _Dispatch(ps, modules, _JonPySendXML, _JonPySendFault, **kw) if __name__ == '__main__': print _copyright Modified: trunk/zsi/ZSI/fault.py =================================================================== --- trunk/zsi/ZSI/fault.py 2006-10-17 19:32:22 UTC (rev 1267) +++ trunk/zsi/ZSI/fault.py 2006-10-17 22:53:19 UTC (rev 1268) @@ -47,7 +47,9 @@ self.any = detail ZSIHeaderDetail.typecode =\ - Struct(ZSIHeaderDetail, [AnyElement(aname='any')], pname=(ZSI_SCHEMA_URI, 'detail')) + Struct(ZSIHeaderDetail, + [AnyElement(aname='any', minOccurs=0, maxOccurs=UNBOUNDED)], + pname=(ZSI_SCHEMA_URI, 'detail')) class ZSIFaultDetailTypeCode(ElementDeclaration, Struct): @@ -132,8 +134,8 @@ actor=None, detail=None, headerdetail=None): if detail is not None and type(detail) not in _seqtypes: detail = (detail,) - #if headerdetail is not None and type(headerdetail) not in _seqtypes: - # headerdetail = (headerdetail,) + if headerdetail is not None and type(headerdetail) not in _seqtypes: + headerdetail = (headerdetail,) self.code, self.string, self.actor, self.detail, self.headerdetail = \ code, string, actor, detail, headerdetail ZSIException.__init__(self, code, string, actor, detail, headerdetail) Modified: trunk/zsi/doc/c08-fault.tex =================================================================== --- trunk/zsi/doc/c08-fault.tex 2006-10-17 19:32:22 UTC (rev 1267) +++ trunk/zsi/doc/c08-fault.tex 2006-10-17 22:53:19 UTC (rev 1268) @@ -62,13 +62,10 @@ A text string holding the value of the SOAP \code{faultstring} element. \end{memberdesc} -\begin{methoddesc}{AsSOAP}{\optional{output\optional{, **kw}}} +\begin{methoddesc}{AsSOAP}{\optional{, **kw}} This method serializes the \class{Fault} object into a SOAP message. -If the \var{output} parameter is not specified or \constant{None}, the -message is returned as a string. -Any other keyword arguments are passed to the \class{SoapWriter} constructor. -Otherwise \method{AsSOAP()} will call \code{\var{output}.write()} as needed -to output the message. +The message is returned as a string. +Any keyword arguments are passed to the \class{SoapWriter} constructor. \versionadded{1.1; the old \method{AsSoap()} method is still available} \end{methoddesc} @@ -83,8 +80,8 @@ \end{methoddesc} \begin{methoddesc}{serialize}{sw} -This method outputs the fault object onto the \var{sw} object, -which must support a \method{write()} method. +This method outputs the fault object onto the \var{sw} object, which is a +\class{SoapWriter} instance. \end{methoddesc} Some convenience functions are available to create a \class{Fault} Modified: trunk/zsi/doc/c10-dispatch.tex =================================================================== --- trunk/zsi/doc/c10-dispatch.tex 2006-10-17 19:32:22 UTC (rev 1267) +++ trunk/zsi/doc/c10-dispatch.tex 2006-10-17 22:53:19 UTC (rev 1268) @@ -33,29 +33,50 @@ \section{Dispatching} -The \module{ZSI.dispatch} module allows you to expose Python functions as a -web service. -The module provides the infrastructure to parse the request, dispatch -to the appropriate handler, and then serialize any return value -back to the client. -The value returned by the function will be serialized back to the client. -To return multiple values, return a list. +The \module{ZSI.dispatch} module allows you to expose Python functions as a web +service. The module provides the infrastructure to parse the request, dispatch +to the appropriate handler, and then serialize any return value back to the +client. The value returned by the function will be serialized back to the +client. If an exception occurs, a SOAP fault will be sent back to the client. -If an exception occurs, a SOAP fault will be sent back to the client. +\subsection{Dispatch Behaviors} By default the callback is invoked with the +pyobj representation of the body root element, and it is expected to return a +self-describing request (w/typecode). Parsing is done via a typecode from +typesmodule, or Any. Other keyword options are available in dispatch mechanisms +(see below) that result in different behavior. +\subsubsection{rpc} An rpc service will ignore the body root (RPC Wrapper) of +the request, and parse all "parts" of message via individual typecodes. The +callback function is expected to return the parts of the message in a dict or a +list. The dispatch mechanism will try to serialize it as a Struct but if this +is not possible it will be serialized as an Array. Parsing done via a typecode +from typesmodule, or Any. Not compatible with \var{docstyle}. + +\subsubsection{docstyle} Callback is invoked with a ParsedSoap instance +representing the request, and the return value is serialized with an XML +typecode (DOM). The result in wrapped as an rpc-style message, with +\emph{Response} appended to the request wrapper. Not compatible with \var{rpc}. + +\subsection{Special Modules} These are keyword options available to all +dispatch mechansism (see below). + +\subsubsection{modules}{Dispatch is based solely on the name of the root element in the +incoming SOAP request; the request URL is ignored. These modules will be search +for a matching function. If no modules are specified, only the +\module{__main__} module will be searched.} + +\subsubsection{typesmodule}{Used for parsing. This module should contain class +definitions with the \code{typecode} attribute set to a \class{TypeCode} +instance. By default, a class definition matching the root element name will be +retrieved or the Any typecode will be used. If using \emph{rpc}, each child of +the root element will be used to retrieve a class definition of the same name.} + +\subsection{Dispatch Mechanisms} Three dispatch mechanisms are provided: one supports standard CGI scripts, one runs a dedicated server based on the \module{BaseHTTPServer} module, and the third uses the JonPY package, \url{http://jonpy.sourceforge.net}, to support FastCGI. -\begin{methoddesc}{AsCGI}{\optional{module_list}} -This method parses the CGI input and invokes a function that has the -same name as the top-level SOAP request element. -The optional \code{module_list} parameter can specify a list of modules -(already imported) to search for functions. -If no modules are specified, only the \module{__main__} module will be searched. -\end{methoddesc} - \begin{methoddesc}{AsServer}{\optional{**keywords}} This creates a \class{HTTPServer} object with a request handler that only supports the ``POST'' method. @@ -66,28 +87,84 @@ The following keyword arguments may be used: \begin{tableiii}{l|c|p{30em}}{textrm}{Keyword}{Default}{Description} -\lineiii{\code{docstyle}}{\code{0}}{If true, then all methods are -invoked with a single argument, the unparsed body of the SOAP message.} +\lineiii{\code{port}}{\code{80}}{Port to listen on.} +\lineiii{\code{addr}}{\code{''}}{Address to listen on.} +\lineiii{\code{docstyle}}{\code{False}}{Exhibit the \emph{docstyle} behavior.} +\lineiii{\code{rpc}}{\code{False}}{Exhibit the \emph{rpc} behavior.} \lineiii{\code{modules}}{\code{(__main__,)}}{List of modules containing functions that can be invoked.} -\lineiii{\code{nsdict}}{\code{\{\}}}{Namespace dictionary to send in the - SOAP \code{Envelope}} -\lineiii{\code{port}}{\code{80}}{Port to listen on.} +\lineiii{\code{typesmodule}}{\code{(__main__,)}}{This module is used for +parsing, it contains class definitions that specify the \code{typecode} +attribute.} +\lineiii{\code{nsdict}}{\code{\{\}}}{Namespace dictionary to send in the SOAP +\code{Envelope}} \end{tableiii} \end{methoddesc} -\begin{methoddesc}{AsJonPy}{request=req\optional{, **keywords}} +\begin{methoddesc}{AsCGI}{\optional{**keywords}} +This method parses the CGI input and invokes a function that has the +same name as the top-level SOAP request element. + +The following keyword arguments may be used: + +\begin{tableiii}{l|c|p{30em}}{textrm}{Keyword}{Default}{Description} +\lineiii{\code{rpc}}{\code{False}}{Exhibit the \emph{rpc} behavior.} +\lineiii{\code{modules}}{\code{(__main__,)}}{List of modules containing +functions that can be invoked.} +\lineiii{\code{typesmodule}}{\code{(__main__,)}}{This module is used for +parsing, it contains class definitions that specify the \code{typecode} +attribute.} +\lineiii{\code{nsdict}}{\code{\{\}}}{Namespace dictionary to send in the SOAP +\code{Envelope}} +\end{tableiii} + +\end{methoddesc} + + + +\begin{methoddesc}{AsHandler}{request=None\optional{, **keywords}} + This method is used within a JonPY handler to do dispatch. The following keyword arguments may be used: \begin{tableiii}{l|c|p{30em}}{textrm}{Keyword}{Default}{Description} -\lineiii{\code{request}}{\code{(__main__,)}}{List of modules containing +\lineiii{\code{request}}{\code{None}}{modpython HTTPRequest instance.} +\lineiii{\code{modules}}{\code{(__main__,)}}{List of modules containing functions that can be invoked.} +\lineiii{\code{docstyle}}{\code{False}}{Exhibit the \emph{docstyle} behavior.} +\lineiii{\code{rpc}}{\code{False}}{Exhibit the \emph{rpc} behavior.} +\lineiii{\code{typesmodule}}{\code{(__main__,)}}{This module is used for +parsing, it contains class definitions that specify the \code{typecode} +attribute.} +\lineiii{\code{nsdict}}{\code{\{\}}}{Namespace dictionary to send in the SOAP +\code{Envelope}} \end{tableiii} +\end{methoddesc} + +\begin{methoddesc}{AsJonPy}{request=None\optional{, **keywords}} + +This method is used within a JonPY handler to do dispatch. + +The following keyword arguments may be used: + +\begin{tableiii}{l|c|p{30em}}{textrm}{Keyword}{Default}{Description} +\lineiii{\code{request}}{\code{None}}{jonpy Request instance.} +\lineiii{\code{modules}}{\code{(__main__,)}}{List of modules containing +functions that can be invoked.} +\lineiii{\code{docstyle}}{\code{False}}{Exhibit the \emph{docstyle} behavior.} +\lineiii{\code{rpc}}{\code{False}}{Exhibit the \emph{rpc} behavior.} +\lineiii{\code{typesmodule}}{\code{(__main__,)}}{This module is used for +parsing, it contains class definitions that specify the \code{typecode} +attribute.} +\lineiii{\code{nsdict}}{\code{\{\}}}{Namespace dictionary to send in the SOAP +\code{Envelope}} +\end{tableiii} + + The following code shows a sample use: \begin{verbatim} @@ -104,6 +181,8 @@ \end{methoddesc} +\subsection{Other Dispatch Stuff} + \begin{methoddesc}{GetClientBinding}{} More sophisticated scripts may want to use access the client binding object, which encapsulates all information about the client invoking the script. @@ -140,6 +219,7 @@ This is most useful when \method{AsCGI()} is used. \end{memberdesc} + \section{The \module{client} module --- sending SOAP messages} \ZSI{} includes a module to connect to a SOAP server over HTTP, send requests, @@ -149,7 +229,9 @@ It must be explicitly imported, as in \samp{from ZSI.client import AUTH,Binding}. -\begin{classdesc}{Binding}{\optional{**keywords}} +\subsection{_Binding} + +\begin{classdesc}{_Binding}{\optional{**keywords}} This class encapsulates a connection to a server, known as a \emph{binding}. A single binding may be used for multiple RPC calls. Between calls, modifiers may be used to change the URL being posted to, @@ -177,18 +259,20 @@ \lineiii{\code{transport}}{HTTPConnection/HTTPSConnection}{transport class} \lineiii{\code{transdict}}{\{\}}{keyword arguments for connection initialization} \lineiii{\code{url}}{n/a}{URL to post to.} +\lineiii{\code{wsAddressURI}}{None}{URI, identifies the WS-Address specification +to use. By default it's not used.} +\lineiii{\code{sig_handler}}{None}{XML Signature handler, must sign and verify.} \end{tableiii} If using SSL, the \code{cert_file} and \code{key_file} keyword parameters may -also be used. -For details see the documentation for the \module{httplib} module. +also be used. For details see the documentation for the \module{httplib} +module. \end{classdesc} -Once a \class{Binding} object has been created, the following modifiers are -available. -All of them return the binding object, so that multiple modifiers can -be chained together. +Once a \class{_Binding} object has been created, the following modifiers are +available. All of them return the binding object, so that multiple modifiers +can be chained together. \begin{methoddesc}{AddHeader}{header, value} Output the specified \code{header} and \code{value} with the HTTP @@ -317,15 +401,42 @@ A text string containing the HTTP reply text. \end{memberdesc} -Finally, if an attribute is fetched other than one of those described -above, it is taken to be the \code{opname} of a remote procedure, -and a callable object is returned. -This object dynamically parses its arguments, receives the reply, and -parses that. +\subsection{Binding} +If an attribute is fetched other than one of those described in +\class{_Binding}, it is taken to be the \code{opname} of a remote procedure, and +a callable object is returned. This object dynamically parses its arguments, +receives the reply, and parses that. -\begin{methoddesc}{opname}{args...} -Using this shortcut requires that the \method{SetURL()} was invoked first. -This method is then equivalent to: -\samp{RPC(None, opname, tuple(args), TC.Any())} +\begin{classdesc}{Binding}{\optional{**keywords}} +For other keyword arguments see \class{_Binding}. +\begin{tableiii}{l|c|p{20em}}{textrm}{Keyword}{Default}{Description} +\lineiii{\code{typesmodule}}{\code{None}}{See explanation in Dispatching} +\end{tableiii} +\end{classdesc} + +\begin{methoddesc}{opname}{*args} +Using this shortcut requires that the \var{url} attribute is set, either +throught the constructor or \method{SetURL()}. \end{methoddesc} + +\subsection{NamedParamBinding} +If an attribute is fetched other than one of those described +in \class{_Binding}, it is taken to be the \code{opname} of a remote procedure, and a callable +object is returned. This object dynamically parses its arguments, receives the +reply, and parses that. + +\begin{classdesc}{NamedParamBinding}{\optional{**keywords}} +For other keyword arguments see \class{_Binding}. +\begin{tableiii}{l|c|p{20em}}{textrm}{Keyword}{Default}{Description} +\lineiii{\code{typesmodule}}{\code{None}}{See explanation in Dispatching} +\end{tableiii} +\end{classdesc} + +\begin{methoddesc}{opname}{**kwargs} +Using this shortcut requires that the \var{url} attribute is set, either +throught the constructor or \method{SetURL()}. +\end{methoddesc} + + + Modified: trunk/zsi/test/test_t4.py =================================================================== --- trunk/zsi/test/test_t4.py 2006-10-17 19:32:22 UTC (rev 1267) +++ trunk/zsi/test/test_t4.py 2006-10-17 22:53:19 UTC (rev 1268) @@ -27,23 +27,23 @@ r = resolvers.NetworkResolver(['http:']) ps = ParsedSoap(IN, resolver=r.Resolve) except ParseException, e: - FaultFromZSIException(e).AsSOAP(OUT) + print >>OUT, FaultFromZSIException(e).AsSOAP() self.fail() except Exception, e: # Faulted while processing; assume it's in the header. - FaultFromException(e, 1, sys.exc_info()[2]).AsSOAP(OUT) + print >>OUT, FaultFromException(e, 1, sys.exc_info()[2]).AsSOAP() self.fail() print 'resolving' typecode = TC.Struct(None, [ TC.XML('xmltest'), TC.String('stringtest', resolver=r.Opaque), ]) try: dict = ps.Parse(typecode) - #except EvaluateException, e: - # FaultFromZSIException(e).AsSOAP(OUT) - # sys.exit(1) + except EvaluateException, e: + print >>OUT, FaultFromZSIException(e).AsSOAP() + self.fail() except Exception, e: # Faulted while processing; now it's the body - FaultFromException(e, 0, sys.exc_info()[2]).AsSOAP(OUT) + print >>OUT, FaultFromException(e, 0, sys.exc_info()[2]).AsSOAP() self.fail() PrettyPrint(dict['xmltest']) print '**', dict['stringtest'], '**' Modified: trunk/zsi/test/test_t6.py =================================================================== --- trunk/zsi/test/test_t6.py 2006-10-17 19:32:22 UTC (rev 1267) +++ trunk/zsi/test/test_t6.py 2006-10-17 22:53:19 UTC (rev 1268) @@ -19,19 +19,19 @@ cid = resolvers.MIMEResolver(m['content-type'], istr) xml = cid.GetSOAPPart() ps = ParsedSoap(xml, resolver=cid.Resolve) - #except ParseException, e: - # FaultFromZSIException(e).AsSOAP(OUT) - # self.fail() + except ParseException, e: + print >>OUT, FaultFromZSIException(e).AsSOAP() + self.fail() except Exception, e: # Faulted while processing; assume it's in the header. - FaultFromException(e, 1, sys.exc_info()[2]).AsSOAP(OUT) + print >>OUT, FaultFromException(e, 1, sys.exc_info()[2]).AsSOAP() self.fail() try: dict = ps.Parse(typecode) except Exception, e: # Faulted while processing; now it's the body - FaultFromException(e, 0, sys.exc_info()[2]).AsSOAP(OUT) + print >>OUT, FaultFromException(e, 0, sys.exc_info()[2]).AsSOAP() self.fail() self.failUnlessEqual(dict['stringtest'], strExtTest, This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |