Examples¶
A detailed, albeit contrived, example of how to use PyXB is in Generating Binding Classes.
Multiple real-world XML and web service examples of varying complexity are
provided in the examples
subdirectories of the PyXB distribution and of the
various bundles. Some are WSDL services, and others are simply XMLSchema
documents. Often there are a couple trivial programs that show how the
bindings are used. The script test.sh
in each directory can be used
to generate the bindings and run the programs in a single step.
See the README.txt
file in each example subdirectory for more
information.
- For ...
- REST-style interactions, see Dictionary (Aonaware), Professional Weather (National Digital Forecast Data), and Thirty Second Example
- SOAP interactions, see Simple Weather (CDyne), Professional Weather (National Digital Forecast Data), and Address-To-Latitude/Longitude (http://geocoder.us)
- Customizing a generated binding, see Television Schedules (Tribune Media Services)
- Using a SAX parser, see Television Schedules (Tribune Media Services)
You may also want to look at some of the unit tests for other ideas.
Dictionary (Aonaware)¶
The Dictionary web service at Aonaware.
The example define.py
looks up a word in all available dictionaries:
from __future__ import print_function
import dict
from pyxb.utils.six.moves.urllib.request import urlopen
import sys
from pyxb.utils import domutils
word = 'xml'
if 1 < len(sys.argv):
word = sys.argv[1]
# Create a REST-style query to retrieve the information about this dictionary.
uri = 'http://services.aonaware.com/DictService/DictService.asmx/Define?word=%s' % (word,)
rxml = urlopen(uri).read()
resp = dict.CreateFromDOM(domutils.StringToDOM(rxml))
print('Definitions of %s:' % (resp.Word,))
for definition in resp.Definitions.Definition:
print('From %s (%s):' % (definition.Dictionary.Name, definition.Dictionary.Id))
print(definition.WordDefinition)
print()
The example showdict.py
lists the available dictionaries:
# -*- coding: utf-8 -*-
from __future__ import print_function
import sys
import dict
from pyxb.utils.six.moves.urllib.request import urlopen
import pyxb.utils.domutils as domutils
from xml.dom import minidom
# Get the list of dictionaries available from the service.
port_uri = 'http://services.aonaware.com/DictService/DictService.asmx'
uri = port_uri + '/DictionaryList'
dle_xml = urlopen(uri).read()
dle_dom = domutils.StringToDOM(dle_xml)
dle = dict.ArrayOfDictionary.createFromDOM(dle_dom)
op_path = '/DictionaryInfo'
for d in dle.Dictionary:
# Create a REST-style query to retrieve the information about this dictionary.
uri = '%s%s?dictId=%s' % (port_uri, op_path, d.Id)
resp = urlopen(uri).read()
print("%s (%s) : %d chars" % (d.Name, d.Id, len(resp)));
# The response is a simple type derived from string, so we can
# just extract and print it. Excluded by default since it has
# leading and trailing whitespace that causes problems with using
# git to store the expected output.
di_resp = dict.CreateFromDOM(domutils.StringToDOM(resp))
if sys.stdout.isatty():
print("%s\n" % di_resp);
Sorry, no automatic generation of classes corresponding to the WSDL operations. Next release, maybe.
Simple Weather (CDyne)¶
A free weather service.
The REST interface was demonstrated as the Thirty Second Example. A
SOAP interface example is in client.py
:
from __future__ import print_function
import sys
import time
from pyxb.utils.six.moves.urllib import request as urllib_request
import pyxb.bundles.wssplat.soap11 as soapenv
import weather
zip = 55113
if 1 < len(sys.argv):
zip = int(sys.argv[1])
# Create an envelope, and give it a body that is the request for the
# service we want.
env = soapenv.Envelope(soapenv.Body(weather.GetCityForecastByZIP(ZIP=str(zip))))
open('request.xml', 'w').write(env.toxml("utf-8"))
# Invoke the service
uri = urllib_request.Request('http://wsf.cdyne.com/WeatherWS/Weather.asmx',
env.toxml("utf-8"),
{ 'SOAPAction' : "http://ws.cdyne.com/WeatherWS/GetCityForecastByZIP", 'Content-Type': 'text/xml' } )
rxml = urllib_request.urlopen(uri).read()
open('response.xml', 'w').write(rxml)
# Convert the response to a SOAP envelope, then extract the actual
# response from the wildcard elements of the body. Note that because
# the weather namespace was registered, PyXB already created the
# binding for the response.
soap_resp = soapenv.CreateFromDocument(rxml)
resp = soap_resp.Body.wildcardElements()[0]
fc_return = resp.GetCityForecastByZIPResult
if fc_return.Success:
print('Got response for %s, %s:' % (fc_return.City, fc_return.State))
for fc in fc_return.ForecastResult.Forecast:
when = time.strftime('%A, %B %d %Y', fc.Date.timetuple())
outlook = fc.Desciption # typos in WSDL left unchanged
low = fc.Temperatures.MorningLow
high = fc.Temperatures.DaytimeHigh
print(' %s: %s, from %s to %s' % (when, outlook, low, high))
Note the various misspellings in the schema (e.g., “Desciption”). Also, be aware that the weather information in this service does not get updated often, and sometimes fails to provide valid dates. Try various zip codes; usually you can find one that works.
Professional Weather (National Digital Forecast Data)¶
Interact with the National Digital Forecast Database.
Use the genbindings.sh
script to retrieve the schema for Digital
Weather Markup Language and generate the bindings. Note that the schema has
two levels of include
directives, which PyXB follows.
The examples for this service are too long to include into the web
documentation. forecast.py
uses the REST interface to get the
forecast temperature data for two locations, and print it in several ways.
latlon.py
does something similar but for a latitude/longitude pair,
using SOAP, and requesting more data.
Television Schedules (Tribune Media Services)¶
A commercial service for television listings.
Only one sample document is available for testing; it is retrieved using
genbindings.sh
. The dumpsample.py
demonstrates extending a
binding to add a custom method, and parsing content with both DOM and SAX.
It also provides timing information; the document is about 200KB, and takes
several seconds to parse.
from __future__ import print_function
import io
import time
import xml.dom.minidom
import pyxb.utils.saxdom
import pyxb.binding.saxer
import tmstvd
#import cProfile
# Extend the anonymous class used by the xtvd element to add a method
# we can use to test equality of two instances. Normally, you'd just
# refer to the complex type binding class itself, but we don't know
# what PyXB named it.
class my_xtvd (tmstvd.xtvd.typeDefinition()):
def equal (self, other, verbose=False):
if len(self.stations.station) != len(other.stations.station):
return False
for i in range(len(self.stations.station)):
s = self.stations.station[i]
o = other.stations.station[i]
if (s.callSign != o.callSign) or (s.name != o.name) or (s.id != o.id):
return False
if verbose:
print('Match station %s is %s, id %d' % (s.callSign, s.name, s.id))
return True
tmstvd.xtvd.typeDefinition()._SetSupersedingClass(my_xtvd)
# The sample document.
xml_file = 'tmsdatadirect_sample.xml'
print('Generating binding from %s with minidom' % (xml_file,))
mt1 = time.time()
xmld = open(xml_file, 'rb').read()
mt2 = time.time()
dom = xml.dom.minidom.parse(io.BytesIO(xmld))
mt3 = time.time()
#cProfile.run('tmstvd.CreateFromDOM(dom.documentElement)', 'dom.prf')
dom_instance = tmstvd.CreateFromDOM(dom.documentElement)
print('minidom first callSign at %s' %(dom_instance.stations.station[0].callSign._location(),))
mt4 = time.time()
print('Generating binding from %s with SAXDOM' % (xml_file,))
dt1 = time.time()
dom = pyxb.utils.saxdom.parse(io.BytesIO(xmld), location_base=xml_file)
dt2 = time.time()
#cProfile.run('tmstvd.CreateFromDOM(dom.documentElement)', 'saxdom.prf')
saxdom_instance = tmstvd.CreateFromDOM(dom.documentElement)
print('SAXDOM first callSign at %s' % (saxdom_instance.stations.station[0].callSign._location(),))
dt3 = time.time()
print('Generating binding from %s with SAX' % (xml_file,))
st1 = time.time()
saxer = pyxb.binding.saxer.make_parser(location_base=xml_file)
handler = saxer.getContentHandler()
st2 = time.time()
saxer.parse(io.BytesIO(xmld))
#cProfile.run('saxer.parse(open(xml_file))', 'sax.prf')
st3 = time.time()
sax_instance = handler.rootObject()
print('SAXER first callSign at %s' % (sax_instance.stations.station[0].callSign._location(),))
print('DOM-based read %f, parse %f, bind %f, total %f' % (mt2-mt1, mt3-mt2, mt4-mt3, mt4-mt2))
print('SAXDOM-based parse %f, bind %f, total %f' % (dt2-dt1, dt3-dt2, dt3-dt1))
print('SAX-based read %f, parse and bind %f, total %f' % (st2-st1, st3-st2, st3-st1))
print("Equality test on DOM vs SAX: %s" % (dom_instance.equal(sax_instance),))
print("Equality test on SAXDOM vs SAX: %s" % (saxdom_instance.equal(sax_instance, verbose=True),))
Address-To-Latitude/Longitude (http://geocoder.us)¶
This service provides the latitude and longitude for free-form US addresses. It also demonstrates several of the pitfalls of using WSDL, which has a very lax concept of schemas, with a system that expects to operate on validatable documents. The following changes were made to make the service easier to work with:
- Change the element form default to
qualified
. This is necessary because there is a non-absent target namespace in the schema, and the documents returned from the service set it as the default namespace. This causes the XML engine to associate that namespace with locally-scoped elements like “number”, while the originalunqualified
form default caused the schema to record them with no namespace.- Set
minOccurs
on all the elements, since some are missing from some responses- Set
nillable
on all elements that are observed to usexsi:nil="true"
in response documents- Provide types and elements corresponding to the request and response messages, since PyXB’s WSDL support does not currently generate them from the operation messages.
genbindings.sh
applies changes to the WSDL after retrieving it and
prior to generating the bindings.
A second complication is the need to burrow down through wildcard elements in the binding instance generated from the SOAP response. This is the consequence of an error in the WSDL specification, which was discovered after too many tools had already worked around it. Currently, PyXB also requires that you work around it manually, although a customization of the relevant SOAP encoding class could make it unnecessary.
from __future__ import print_function
import sys
from pyxb.utils.six.moves.urllib import request as urllib_request
import GeoCoder
from pyxb import BIND
from pyxb.utils import domutils
import pyxb.bundles.wssplat.soap11 as soapenv
import pyxb.bundles.wssplat.soapenc as soapenc
address = '1600 Pennsylvania Ave., Washington, DC'
if 1 < len(sys.argv):
address = sys.argv[1]
env = soapenv.Envelope(Body=BIND(GeoCoder.geocode(address)))
uri = urllib_request.Request('http://rpc.geocoder.us/service/soap/',
env.toxml("utf-8"),
{ 'SOAPAction' : "http://rpc.geocoder.us/Geo/Coder/US#geocode", 'Content-Type': 'text/xml' } )
rxml = urllib_request.urlopen(uri).read()
#open('response.xml', 'w').write(rxml)
#rxml = open('response.xml').read()
response = soapenv.CreateFromDocument(rxml)
# OK, here we get into ugliness due to WSDL's concept of schema in the
# SOAP encoding not being consistent with XML Schema, even though it
# uses the same namespace. See
# http://tech.groups.yahoo.com/group/soapbuilders/message/5879. In
# short, the WSDL spec shows an example using soapenc:Array where a
# restriction was used to set the value of the wsdl:arrayType
# attribute. This restriction failed to duplicate the element content
# of the base type, resulting in a content type of empty in the
# restricted type. Consequently, PyXB can't get the information out
# of the DOM node, and we have to skip over the wildcard items to find
# something we can deal with.
# As further evidence the folks who designed SOAP 1.1 didn't know what
# they were doing, the encodingStyle attribute that's supposed to go
# in the Envelope can't validly be present there, since it's not
# listed and it's not in the namespace admitted by the attribute
# wildcard. Fortunately, PyXB doesn't currently validate wildcards.
encoding_style = response.wildcardAttributeMap().get(soapenv.Namespace.createExpandedName('encodingStyle'))
items = []
if encoding_style == soapenc.Namespace.uri():
gcr = response.Body.wildcardElements()[0]
soap_array = gcr.wildcardElements()[0]
items = soap_array.wildcardElements()
else:
pass
for item in items:
if (item.lat is None) or item.lat._isNil():
print('Warning: Address did not resolve')
print('''
%s %s %s %s %s
%s, %s %s
%s %s''' % (item.number, item.prefix, item.street, item.type, item.suffix,
item.city, item.state, item.zip,
item.lat, item.long))
OpenGIS (http://www.opengeospatial.org)¶
See the directory examples/OpenGIS
in the distribution.