1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
|
The "Suds" web services client is a lightweight soap-based client for python.
The primary classes in the package are the:
* ServiceProxy (serviceproxy.py)
* WSDL (wsdl.py)
Basic features:
* no class generation
* provides an object-like API.
* reads wsdl at runtime for encoding/decoding
* provides for the following SOAP (style) binding/encoding:
* Document/Literal
* RPC/Literal
* RPC/Encoded (section 5)
Logging:
By default, the suds package logs at LEVEL=INFO using a console handler.
All messages are at level DEBUG or ERROR so, no messages are written
unless the user does the following: suds.logger(<desired package>).setLevel(logging.<desired-level>)
A common example (show sent/received soap messages):
>
> suds.logger('serviceproxy').setLevel(logging.DEBUG)
>
Basic usage:
You will need to know the url for WSDL for each service used. Simply create
a proxy for that service as follows:
> from serviceproxy import ServiceProxy
>
> myurl = 'http://localhost:7080/webservices/WebServiceTestBean?wsdl'
>
> myservice = ServiceProxy(url)
You can inspect service object with: __str()__ as follows to get a list of
methods provide by the service:
>
> print myservice
>
service (WebServiceTestBeanService)
prefixes:
SOAP-ENC = "http://schemas.xmlsoap.org/soap/encoding/"
SOAP-ENV = "http://schemas.xmlsoap.org/soap/envelope/"
soap = "http://schemas.xmlsoap.org/wsdl/soap/"
tns = "http://myservice/namespace"
wsdl = "http://schemas.xmlsoap.org/wsdl/"
xsd = "http://www.w3.org/2001/XMLSchema"
xsi = "http://www.w3.org/2001/XMLSchema-instance"
methods:
addPerson(person{tns:person})
echo(arg0{xsd:string})
getPersonByName(name{tns:name})
hello()
testExceptions()
updatePerson(person{tns:anotherPerson}, name{tns:name})
The sample ouput lists that the service named "WebServiceTestBeanService"
has methods such as addPerson() which takes a 'person' argument of type: 'person'.
This is listed as:
addPerson(person{person}) where parameter name is listed and followed by it's
type as {person}. There is a type (or class) named 'person'.
So, to get person object to pass as an argument we need to get a person argument
as follows:
>
> factory = myservice.__factory__
> person = factory.get_instance('person')
> print person
>
{
phone = []
age = NONE
name =
{
last = NONE
first = NONE
}
}
As you can see, the object is created as defined by the WSDL. The list of phone
number is empty so we'll have to create one:
>
> phone = factory.get_instance('phone')
> phone.npa = 202
> phone.nxx = 555
> phone.number = 1212
>
... and the name and age need to be set and we need to create a
name object first:
>
> name = factory.get_instance('name')
> name.first = 'Elmer'
> name.last = 'Fudd'
>
Now, let's set the properties of our person object
>
> person.name = name
> person.age = 35
> person.phone = [phone]
>
... and invoke our method named addPerson() as follows:
>
> try:
> person_added = myservice.addPerson(person)
> except WebFault, e:
> print e
>
It's that easy.
The ServiceProxy can be configured to throw web faults as WebFault or
to return a tuple (<status>, <returned-value>) instead as follows:
>
> myservice = ServiceProxy(url, faults=False)
> result = myservice.addPerson(person)
> print result
( 200, person ...)
Enumerations are handled as follows:
Let's say the wsdl defines the following enumeration,
<xs:simpleType name="resourceCategory">
<xs:restriction base="xs:string">
<xs:enumeration value="PLATFORM"/>
<xs:enumeration value="SERVER"/>
<xs:enumeration value="SERVICE"/>
</xs:restriction>
</xs:simpleType>
The service proxy can instantiate the enumeration so it can be
used. Misspelled references to elements of the enum will raise a
AttrError exception as:
>
> resourceCategory = factory.get_enum('resourceCategory') <--- None if not found.
> myservice.getResourceByCategory(resourceCategory.PLATFORM)
>
The get_instance() method should always be used becuase it returns objects that already
have the proper structure and xsi:type="" attribute defined. Since xsd supports nested type
definition, so does get_instance() using the (.) dot notation. For example suppose the (name)
type was not defined as a top level "named" type but rather defined within the (person) type.
In this case creating a (name) object would have to be quanified by it's parent's name using the
dot notation as follows:
>
> name = factory.get_instance('person.name')
>
NOTE FOR AXIS USERS
Axis, by default, uses MultiRefs when marshalling objects,
but suds does not yet support multirefs. If you are using a SOAP
service provided by Axis, you must change the Axis global configuration and
set the global parameter "sendMultiRefs" to false.
See http://ws.apache.org/axis/java/reference.html#GlobalAxisConfiguration for
an explanation about Axis global configuration.
AUTHENTICATION
Revision 63+ (and release 0.1.8+) includes the migration from httplib to urllib2
which enables users to leverage all of the authentication features provided
by urllib2. For example basic http authentication could be implemented
as follows:
> import urllib2
>
> theurl = 'www.someserver.com/toplevelurl/somepage.htm'
> protocol = 'http://'
> username = 'johnny'
> password = 'XXXXXX'
> # a great password
>
> passman = urllib2.HTTPPasswordMgrWithDefaultRealm()
> # this creates a password manager
> passman.add_password(None, theurl, username, password)
> # because we have put None at the start it will always
> # use this username/password combination for urls
> # for which `theurl` is a super-url
>
> authhandler = urllib2.HTTPBasicAuthHandler(passman)
> # create the AuthHandler
>
> opener = urllib2.build_opener(authhandler)
>
> urllib2.install_opener(opener)
> # All calls to urllib2.urlopen will now use our handler
> # Make sure not to include the protocol in with the URL, or
> # HTTPPasswordMgrWithDefaultRealm will be very confused.
> # You must (of course) use it when fetching the page though.
>
> pagehandle = urllib2.urlopen(protocol + theurl)
Since suds uses urllib2.urlopen(), basic http authentication is handled
automatically for you if you setup the urllib2 library correctly.
PROXIES
Suds handles proxies using urllib2.Request.set_proxy(). The proxy
flag can be passed to the ServiceProxy constructor as kwarg. The proxy
arg must contain a dictionary where keys=protocols and values are
the hostname (or IP) and port of the proxy.
> ...
> proxy = dict(http='host:80', https='host:443', ...)
> myservice = ServiceProxy(url, proxy=proxy)
> ...
NIL VALUES
Some web service endpoints can handle nil values as <tag xsi:nil="true"/> and
others cannot. The nil_supported flag (default: True) on the
ServiceProxy specifies weather Object values = None are sent to the
WS server in the soap message as <tag xsi:nil="true"/> or <tag/>.
TECHNICAL (FYI) NOTES
* XML namespaces are represented as a tuple (prefix, URI).
The default namespace is (None,None).
* The suds.sax module was written becuase elementtree and other
python XML packages either: have a DOM API which is very unfriendly
or: (in the case of elementtree) do not deal with namespaces and
especially prefixes sufficiently.
* A qualified reference is a type that is referenced in the WSDL such as <tag type="tns:Person/>
where the qualified reference is a tuple ('Person', ('tns','http://myservce/namespace'))
where the namespace is the 2nd part of the tuple. When a prefix is not supplied as in
<tag type="Person/>, the namespace is the targetNamespace for the defining fragment.
This ensures that all lookups and comparisons are fully qualified.
TIPS
* Cache the ServiceProxy because reading and digesting the WSDL can be expensive
because some servers generate them on demand.
RELEASE-NOTES:
=================================================
version-0.1.1 (12-17-07):
This release marks the first release in fedora.
version-0.1.2 (12-18-07):
This release contains an update to property adds:
* metadata support
* overrides: __getitem__, __setitem__, __contans__
* changes property(reader|writer) to use the property.metadata
to handle namespaces for XML documents.
* fixes setup.py requires.
version-0.1.3 (12-19-07):
* Fixes problem where nodes marked as a collection (maxOccurs > 1) not
creating property objects with value=[] when mapped-in with < 2
values by the DocumentReader. Caused by missing the
bindings.Document.ReplyHint.stripns() (which uses the DocumentReader.stripns())
conversion to DocumentReader.stripn() now returning a tuple (ns,tag) as
of 0.1.2.
version-0.1.4 (12-21-07):
* Provides for service method parameters to be None.
* Add proper handling of method params that are lists of property
objects.
version-0.1.5( 02-21-08 ):
* Provides better logging in the modules get logger by hierarchal names.
* Refactored as needed to truely support other bindings.
* Add sax module which replaces ElementTree. This is faster, simpler and
handles namespaces (prefixes) properly.
version-0.1.6 (03-06-08):
* Provides proper handling of wsdls that contain schema sections containing
xsd schema imports: <import namespace="" schemaLocation=""?>. The
referenced schemas are imported when a schemaLocation is specified.
* Raises exceptions for http status codes not already handled.
version-0.1.7 (04-08-08):
* Added Binding.nil_supported to controls how property values (out) = None and empty tag (in) are processed.
* service.binding.nil_supported = True -- means that property values = None are marshalled (out) as
<x xsi:nil=true/> and <x/> is unmarshalled as '' and <x xsi:nil/> is unmarshalled as None.
* service.binding.nil_supported = False -- means that property values = None are marshalled (out) as
<x/> and <x/> *and* <x xsi:nil=true/> is unmarshalled as None.
The xsi:nil is really ignored.
* THE DEFAULT IS (TRUE)
* Sax handler updated to handle multiple character() callbacks when the sax parser "chunks" the text.
When the node.text is None, the node.text is set to the characters. Else, the characters are appended.
Thanks - andrea.spinelli@imteam.it
* Replaced special (text) attribute with __text__ to allow for natural elements named "text"
* Add unicode support by:
* Add __unicode__ to all classes with __str__
* Replace all str() calls with unicode().
* __str__() returns UTF-8 encoded result of __unicode__.
* XML output encoded as UTF-8 which matches the HTTP header and supports unicode.
* SchemaCollection changed to provide the builtin() and custom() methods. To support this, findPrefixes() was added to the
Element in sax.py. This is a better approach anyway since the wsdl and schemas may have many prefixes
to http://www.w3.org/2001/XMLSchema. Tested with both doc/lit and rpc/lit bindings
* Refactored bindings packages from document & rpc to literal & encoded
* Contains the completion of *full* namespace support as follows:
* Namespace prefixes are no longer stripped from attribute values that
reference types defined in the wsdl.
* Schema's imported using <import/> should properly handle namespace and prefix
mapping and re-mapping as needed.
* All types are resolved, using fully qualified (w/ namespaces) lookups.
* Schema.get_type() supports paths with and without ns prefixes. When no prefix
is specified the type is matched using the schema's target namespace.
* Property maintains attribute names (keys) in the order added. This also means
that get_item() and get_names() return ordered values.
( Although, I suspect ordering really needs to be done in the marshaller using the
order specified in the wsdl/schema )
Major refactoring of the schema.py. The primary goals is perparation for type lookups that are
fully qualified by namespace. Once completed, the prefixes on attribute values will not longer
be stripped (purged).
Change Summary:
1) SchemaProperty overlay classes created at __init__ instead of on-demand.
2) schema imports performed by new Import class instead of by Schema.
3) Schema loads top level properties using a factory.
4) All SchemaProperty /children/ lists are sorted by __cmp__ in SchemaProperty derived classes.
This ensures that types with the same name are resolved in the following order (Import, Complex, Simple, Element).
5) All /children/ SchemaProperty lists are constructed at __init__ instead of on-demand.
6) The SchemaGroup created and WSDL class updated. This works better then having the wsdl aggregate the <schema/>
nodes which severs linkage to the wsdl parent element that have namespace prefix mapping.
7) <import/> element handles properly in that both namespace remapping and prefix re-mapping of the imported schema's
targetNamespace and associated prefix mapping - is performed.
Eg: SCHMEA-A has prefix (tns) mapped as xmlns:tns=http://nsA and has targetNamespace=http://nsA.
SCHEMA-B is importing schema A and has prefix (abc) mapped as xmlns:abc=http://nsABC.
SCHEMA-B imports A as <import namespace=http://nsB xxx schemaLocation=http://nsA/schema-a.xsd>.
So, since SCHEMA-B will be referencing elements of SCHEMA-A with prefix (abc) such as abc:something, SCHEMA-A's
targetNamespace must be updated as http://nsABC and all element with type=tns:something must be updated to be
type=abc:something so then can be resolved.
* Fixes unmarshalling problem where nodes are added to property as (text, value). This as introduced when the
bindings were refactored.
* Fixed various Property print problems.
Notes:
Thanks to Jesper Noehr of Coniuro for the majority of the rpc/literal binding and
for lots of collaboration on #suds.
version-0.1.8 (04-28-08):
* Contains the first cut at the rpc/encoded soap style.
* Replaced Property class with suds.sudsobject.Object. The Property class was developed a long time
ago with a slightly different purpose. The suds Object is a simpler (more straight forward) approach that
requires less code and works better in the debugger.
* The Binding (and the encoding) is selected on a per-method basis which is more consistent with the wsdl.
In <= 0.1.7, the binding was selected when the ServiceProxy was constructed and used for all service
methods. The binding was stored as self.binding. Since the WSDL provides for a separate binding style and
encoding for each operation, Suds needed to be change to work the same way.
* The (nil_supported) and (faults) flag(s) passed into the service proxy using **kwargs. In addition to these
flags, a (http_proxy) flag has been added and is passed to the urllib2.Request object. The following args
are supported:
* faults = Raise faults raised by server (default:True), else return tuple from service method invocation
as (http code, object).
* nil_supported = The bindings will set the xsi:nil="true" on nodes that have a value=None when this
flag is True (default:True). Otherwise, an empty node <x/> is sent.
* proxy = An http proxy to be specified on requests (default:{}).
The proxy is defined as {protocol:proxy,}
* Http proxy supported (see above).
* ServiceProxy refactored to delegate to a SoapClient. Since the service proxy exposes web services via getattr(),
any attribute (including methods) provided by the ServiceProxy class hides WS operations defined by the
wsdl. So, by moving everything to the SoapClient, wsdl operations are no longer hidden without
having to use *hoky* names for attributes and methods in the service proxy. Instead, the service proxy has
__client__ and __factory__ attributes (which really should be at low risk for name collision). For now the
get_instance() and get_enum() methods have not been moved to preserve backward compatibility. Although,
the prefered API change would to replace:
> service = ServiceProxy('myurl')
> person = service.get_instance('person')
... with something like ...
> service = ServiceProxy('myurl')
> factory = service.__factory__
> person = factory.get_instance('person')
After a few releases giving time for users to switch the new API, the get_instance() and get_enum()
methods may be removed with a notice in big letters.
* Fixed problem where a wsdl doesn't define a <schema/> section and Suds can't resolve the prefixes for the
http://www.w3.org/2001/XMLSchema namespace to detect builtin types such as (xs:string).
|