diff --git a/.hgignore b/.hgignore
new file mode 100644
index 00000000..58a01737
--- /dev/null
+++ b/.hgignore
@@ -0,0 +1,6 @@
+# use glob syntax.
+syntax: glob
+
+*.pyc
+.project
+.pydevproject
\ No newline at end of file
diff --git a/docs/tutorial.txt b/docs/tutorial.txt
new file mode 100644
index 00000000..d4ed31e0
--- /dev/null
+++ b/docs/tutorial.txt
@@ -0,0 +1,274 @@
+About
+=====
+
+The main purpose of this library is neat implementation of SOAP protocol,
+but xsd package can used for any XML as it gives means of mapping XML
+to object.
+ The object description generally is similar to Django database models - the static
+fields that define instance fields. The main difference would be that type is
+passed as first parameter, rather then being a field e.g
+Django: tail_number = models.CharField()
+ws: tail_number = xsd.Element(xsd.String)
+xsd.Element reflects the nature of the field, elements are fields that
+will be wrapped with tag, other options are xsd.Attribute, xsd.Ref and
+xsd.ListElement. For more detail see xsd.Element pydoc.
+ As SOAP, WSDL and XSD files are also XMLs the xsd package was also
+used to describe them. The descriptions are located in xsdspec.py, soap.py
+and wsdl.py. soap package also provides dispatcher and client Stub.
+ Other elements included in this tool are translators, that can generate python
+code from formal description, or formal description from code. Related files:
+py2xsd.py, xsd2py.py, wsdl2py.py, py2wsdl.py.
+ utils.py is mostly jinja2 helper functions. jinja2 is templating engine used
+for code generation.
+
+1. Working with XML
+================
+ The main building block are xsd.ComplexType, xsd.Element, xsd.Attribute and
+simple types defined in xsd package. xsd.ComplexType is a parent to extend to define
+own type. Main methods for types are xml - translates object into XML, and parsexml
+builds object from XML.
+
+#Example 1. Rendering object to XML.
+from ws import xsd
+
+class Airport(xsd.ComplexType):
+ type = xsd.Element(xsd.String)
+ code = xsd.Element(xsd.String)
+
+airport = Airport()
+airport.type = "IATA"
+airport.code = "WAW"
+print airport.xml("takeoff_airport")
+
+Note that xml method takes one parameter - root tag name.
+
+#Example 2. Parsing XML to object.
+from ws import xsd
+class Airport(xsd.ComplexType):
+ type = xsd.Element(xsd.String)
+ code = xsd.Element(xsd.String)
+XML = """
+ IATA
+ WAW
+"""
+airpport = Airport.parsexml(XML)
+print "Type:", airport.type#prints Type: IATA
+print "Code:", airport.code#Code: WAW
+
+#Example 3. Nested ComplexTypes with attributes.
+from datetime import datetime
+from ws import xsd
+class Airport(xsd.ComplexType):
+ type = xsd.Element(xsd.String)
+ code = xsd.Element(xsd.String)
+
+class Flight(xsd.ComplexType):
+ tail_number = xsd.Attribute(xsd.String)
+ type = xsd.Attribute(xsd.Integer, use=xsd.Use.OPTIONAL)
+ takeoff_airport = xsd.Element(Airport)
+ takeoff_datetime = xsd.Element(xsd.DateTime, minOccurs=0)
+ landing_airport = xsd.Element(Airport)
+ landing_datetime = xsd.Element(xsd.DateTime, minOccurs=0)
+
+flight = Flight(tail_number="LN-KKA")#Constructor handles field inititailization.
+flight.takeoff_airport = Airport(type="IATA", code="WAW")
+flight.landing_airport = Airport(type="ICAO", code="EGLL")
+
+print flight.xml("flight")
+#datetime field types will accept, datetime object or string,
+#that parses correctly to such object.
+flight.takeoff_datetime = datetime.now()
+print flight.xml("flight")
+
+will produce:
+
+
+ IATA
+ WAW
+
+ 2011-05-06T11:11:23
+
+ ICAO
+ EGLL
+
+
+
+2. Schema
+=================
+ xsd.Schema is an object that aggregates all informations stored in XSD file.
+There two main use cases for this object. It can be used to generate XSD file or
+it can be generated from such file. For detail field description see: xsd.Schema
+pydoc. Schema instance is required for validation and because SOAP webservice
+performs validation is required for service configuration too: See documentation
+Defining webservice.
+
+2.1 Generating code from XSD file.
+=================================
+ py2xsd.py generates Python representation of XML from XSD file.
+Example: xsd2py.py Specifications\ops.xsd will generate:
+
+from ws import xsd
+
+class Pilot(xsd.String):
+ enumeration = [ "CAPTAIN", "FIRST_OFFICER", ]
+
+class Airport(xsd.ComplexType):
+ INHERITANCE = None
+ INDICATOR = xsd.Sequence
+ code_type = xsd.Element(xsd.String( enumeration =
+ [ "ICAO", "IATA", "FAA",]) )
+ code = xsd.Element(xsd.String)
+
+
+class Weight(xsd.ComplexType):
+ INHERITANCE = None
+ INDICATOR = xsd.Sequence
+ value = xsd.Element(xsd.Integer)
+ unit = xsd.Element(xsd.String( enumeration =
+ [ "kg", "lb",]) )
+
+
+class Ops(xsd.ComplexType):
+ INHERITANCE = None
+ INDICATOR = xsd.Sequence
+ aircraft = xsd.Element(xsd.String)
+ flight_number = xsd.Element(xsd.String)
+ type = xsd.Element(xsd.String( enumeration =
+ [ "COMMERCIAL", "INCOMPLETE", "ENGINE_RUN_UP", "TEST", "TRAINING", "FERRY",
+"POSITIONING", "LINE_TRAINING",]) )
+ takeoff_airport = xsd.Element(Airport)
+ takeoff_gate_datetime = xsd.Element(xsd.DateTime, minOccurs=0)
+ takeoff_datetime = xsd.Element(xsd.DateTime)
+ takeoff_fuel = xsd.Element(Weight, minOccurs=0)
+ takeoff_gross_weight = xsd.Element(Weight, minOccurs=0)
+ takeoff_pilot = xsd.Element(Pilot, minOccurs=0)
+ landing_airport = xsd.Element(Airport)
+ landing_gate_datetime = xsd.Element(xsd.DateTime, minOccurs=0)
+ landing_datetime = xsd.Element(xsd.DateTime)
+ landing_fuel = xsd.Element(Weight, minOccurs=0)
+ landing_pilot = xsd.Element(Pilot, minOccurs=0)
+ destination_airport = xsd.Element(Airport, minOccurs=0)
+ captain_code = xsd.Element(xsd.String, minOccurs=0)
+ first_officer_code = xsd.Element(xsd.String, minOccurs=0)
+ V2 = xsd.Element(xsd.Integer, minOccurs=0)
+ Vref = xsd.Element(xsd.Integer, minOccurs=0)
+ Vapp = xsd.Element(xsd.Integer, minOccurs=0)
+
+
+class Status(xsd.ComplexType):
+ INHERITANCE = None
+ INDICATOR = xsd.Sequence
+ action = xsd.Element(xsd.String( enumeration =
+ [ "INSERTED", "UPDATED", "EXISTS",]) )
+ id = xsd.Element(xsd.Long)
+
+Schema = xsd.Schema(
+ targetNamespace = "http://flightdataservices.com/ops.xsd",
+ elementFormDefault = "unqualified",
+ simpleTypes = [ Pilot,],
+ attributeGroups = [],
+ groups = [],
+ complexTypes = [ Airport, Weight, Ops, Status,],
+ elements = { "status":xsd.Element(Status), "ops":xsd.Element(Ops),})
+
+
+Let redirect output to the python file.
+{{xsd2py.py Specifications\ops.xsd > tmp\ops.py}}
+Now calling {{py2xsd.py tmp\ops.py}} will generate equivalent XSD from Python
+code. xsd2py script expects schema instance to be defined in global scope
+called "Schema", in way similar to one in generated code.
+
+2. Web service.
+========================
+
+ When WSDL file is provided code can be generated from it. If not, advised
+would be to write to code first a then use browser to request it. Accessing
+?wsdl with browser will give current WSDL with XSD
+embaded.
+
+2.1 Generating code from WSDL file
+==================================
+ wsdl2py can generate either client or server code. For server use -s, client -c
+flag.
+Server example: wsdl2py.py -s Specifications\ops.wsdl
+
+{{{
+...XSD part truncated...
+PutOps_method = xsd.Method(function = PutOps,
+ soapAction = "http://polaris.flightdataservices.com/ws/ops/PutOps",
+ input = "ops",#Pointer to Schema.elements
+ output = "status",#Pointer to Schema.elements
+ operationName = "PutOps")
+
+SERVICE = soap.Service(
+ targetNamespace = "http://flightdataservices.com/ops.wsdl",
+ location = "http://polaris.flightdataservices.com/ws/ops",
+ schema = Schema,
+ methods = [PutOps_method, ])
+
+
+#Comment this lines for py2xsd generation to avoid error message about
+#DJANGO_SETTINGS_MODULE not being set. If authorization is required
+#dispatch can be wrapped with login required in way similar to csrf_exempt.
+from django.views.decorators.csrf import csrf_exempt
+dispatch = csrf_exempt(soap.get_django_dispatch(SERVICE))
+
+#Put this lines in your urls.py:
+#urlpatterns += patterns('',
+# (r"^ws/ops$", ".dispatch")
+#)
+}}}
+
+ Generated code has four main items: methods descriptions, Service description,
+dispatcher and Django ulrs.py binding.
+ Method description describes one method for service(that can consist from
+more then one method). Methods give dispatcher informations required for method
+distinction - soapAction and operationName, and function to call on incoming SOAP message.
+For detail field meaning see xsd.Method pydoc.
+ SERVICE aggregates all informations required for WSDL generation and correct
+dispatching.
+ get_django_dispatch returns a function binded to SERVICE that pointed from
+urls.py will call appropriate function on incoming SOAP message. The called function,
+in this example PutOps, is expected to return object from XSD that could be translated
+to correct and valid response - for this example this would be Status instance.
+ URLs binding it is commented out, paste this code into your urls.py and change
+ to point file where to code was generated.
+
+2.1 Client
+==========
+ Client can be generated with flag -c: wsdl2py.py -c Specifications\ops.wsdl
+
+ Generated code:
+ {{{
+ ...XSD Part truncated ...
+ PutOps_method = xsd.Method(
+ soapAction = "http://polaris.flightdataservices.com/ws/ops/PutOps",
+ input = "ops",#Pointer to Schema.elements
+ output = "status",#Pointer to Schema.elements
+ operationName = "PutOps")
+
+SERVICE = soap.Service(
+ targetNamespace = "http://flightdataservices.com/ops.wsdl",
+ location = "http://polaris.flightdataservices.com/ws/ops",
+ schema = Schema,
+ methods = [PutOps_method, ])
+
+
+class ServiceStub(soap.Stub):
+ SERVICE = SERVICE
+
+ def PutOps(self, ops):
+ return self.call("PutOps", ops)
+ }}}
+
+ ServiceStub is a proxy object that defines methods available on remote webservice.
+ Calling one of those method, in the example there is only one PutOps, will produce SOAP
+call to remote server defined in SERVICE. The methods will return appropriate object
+from XSD description or raise an exception on any problems.
+
+For more real example: See docs/example_client.py
+
+
+
+
+
diff --git a/examples/client.py b/examples/client.py
new file mode 100644
index 00000000..09dee458
--- /dev/null
+++ b/examples/client.py
@@ -0,0 +1,100 @@
+#This was was generated by wsdl2py, try to not edit.
+from soapbox import soap, xsd
+
+class Pilot(xsd.String):
+ enumeration = [ "CAPTAIN", "FIRST_OFFICER", ]
+
+
+class Airport(xsd.ComplexType):
+ INHERITANCE = None
+ INDICATOR = xsd.Sequence
+ code_type = xsd.Element(xsd.String( enumeration =
+ [ "ICAO", "IATA", "FAA",]) )
+ code = xsd.Element(xsd.String)
+
+
+class Weight(xsd.ComplexType):
+ INHERITANCE = None
+ INDICATOR = xsd.Sequence
+ value = xsd.Element(xsd.Integer)
+ unit = xsd.Element(xsd.String( enumeration =
+ [ "kg", "lb",]) )
+
+
+class Ops(xsd.ComplexType):
+ INHERITANCE = None
+ INDICATOR = xsd.Sequence
+ aircraft = xsd.Element(xsd.String)
+ flight_number = xsd.Element(xsd.String)
+ type = xsd.Element(xsd.String( enumeration =
+ [ "COMMERCIAL", "INCOMPLETE", "ENGINE_RUN_UP", "TEST", "TRAINING", "FERRY", "POSITIONING", "LINE_TRAINING",]) )
+ takeoff_airport = xsd.Element(Airport)
+ takeoff_gate_datetime = xsd.Element(xsd.DateTime, minOccurs=0)
+ takeoff_datetime = xsd.Element(xsd.DateTime)
+ takeoff_fuel = xsd.Element(Weight, minOccurs=0)
+ takeoff_gross_weight = xsd.Element(Weight, minOccurs=0)
+ takeoff_pilot = xsd.Element(Pilot, minOccurs=0)
+ landing_airport = xsd.Element(Airport)
+ landing_gate_datetime = xsd.Element(xsd.DateTime, minOccurs=0)
+ landing_datetime = xsd.Element(xsd.DateTime)
+ landing_fuel = xsd.Element(Weight, minOccurs=0)
+ landing_pilot = xsd.Element(Pilot, minOccurs=0)
+ destination_airport = xsd.Element(Airport, minOccurs=0)
+ captain_code = xsd.Element(xsd.String, minOccurs=0)
+ first_officer_code = xsd.Element(xsd.String, minOccurs=0)
+ V2 = xsd.Element(xsd.Integer, minOccurs=0)
+ Vref = xsd.Element(xsd.Integer, minOccurs=0)
+ Vapp = xsd.Element(xsd.Integer, minOccurs=0)
+
+
+class Status(xsd.ComplexType):
+ INHERITANCE = None
+ INDICATOR = xsd.Sequence
+ action = xsd.Element(xsd.String( enumeration =
+ [ "INSERTED", "UPDATED", "EXISTS",]) )
+ id = xsd.Element(xsd.Long)
+
+Schema = xsd.Schema(
+ targetNamespace = "http://flightdataservices.com/ops.xsd",
+ elementFormDefault = "unqualified",
+ simpleTypes = [ Pilot,],
+ attributeGroups = [],
+ groups = [],
+ complexTypes = [ Airport, Weight, Ops, Status,],
+ elements = { "ops":xsd.Element(Ops), "status":xsd.Element(Status),})
+
+
+
+PutOps_method = xsd.Method(
+ soapAction = "http://polaris.flightdataservices.com/ws/ops/PutOps",
+ input = "ops",#Pointer to Schema.elements
+ output = "status",#Pointer to Schema.elements
+ operationName = "PutOps")
+
+SERVICE = soap.Service(
+ targetNamespace = "http://flightdataservices.com/ops.wsdl",
+ location = "http://127.0.0.1:8000/ws/ops",
+ schema = Schema,
+ methods = [PutOps_method, ])
+
+
+class ServiceStub(soap.Stub):
+ SERVICE = SERVICE
+
+ def PutOps(self, ops):
+ return self.call("PutOps", ops)
+
+if __name__ == "__main__":
+ from datetime import datetime
+ stub = ServiceStub()
+ ops = Ops()
+ ops.aircraft = "LN-KKU"
+ ops.flight_number = "1234"
+ ops.type = "COMMERCIAL"
+ ops.takeoff_airport = Airport(code_type="IATA",code="WAW")
+ ops.takeoff_datetime = datetime.now()
+ ops.landing_airport = Airport(code_type="ICAO", code="EGLL")
+ ops.landing_datetime = datetime.now()
+ status = stub.PutOps(ops)
+ print status.action, status.id
+
diff --git a/examples/ops.wsdl b/examples/ops.wsdl
new file mode 100644
index 00000000..1838f9e2
--- /dev/null
+++ b/examples/ops.wsdl
@@ -0,0 +1,126 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Register Flight Ops
+
+
+
+
+
diff --git a/examples/ops.xsd b/examples/ops.xsd
new file mode 100644
index 00000000..df3bd069
--- /dev/null
+++ b/examples/ops.xsd
@@ -0,0 +1,91 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/xml_complex_types.py b/examples/xml_complex_types.py
new file mode 100644
index 00000000..50600a33
--- /dev/null
+++ b/examples/xml_complex_types.py
@@ -0,0 +1,25 @@
+#Example 3. Nested ComplexTypes with attributes.
+from datetime import datetime
+from soapbox import xsd
+
+class Airport(xsd.ComplexType):
+ type = xsd.Element(xsd.String)
+ code = xsd.Element(xsd.String)
+
+class Flight(xsd.ComplexType):
+ tail_number = xsd.Attribute(xsd.String)
+ type = xsd.Attribute(xsd.Integer, use=xsd.Use.OPTIONAL)
+ takeoff_airport = xsd.Element(Airport)
+ takeoff_datetime = xsd.Element(xsd.DateTime, minOccurs=0)
+ landing_airport = xsd.Element(Airport)
+ landing_datetime = xsd.Element(xsd.DateTime, minOccurs=0)
+
+flight = Flight(tail_number="LN-KKA")#Constructor handles field inititailization.
+flight.takeoff_airport = Airport(type="IATA", code="WAW")
+flight.landing_airport = Airport(type="ICAO", code="EGLL")
+
+print flight.xml("flight")
+#datetime field types will accept, datetime object or string,
+#that parses correctly to such object.
+flight.takeoff_datetime = datetime.now()
+print flight.xml("flight")
\ No newline at end of file
diff --git a/examples/xml_parsing.py b/examples/xml_parsing.py
new file mode 100644
index 00000000..89b89a63
--- /dev/null
+++ b/examples/xml_parsing.py
@@ -0,0 +1,15 @@
+#Example 2. Parsing XML to object.
+from soapbox import xsd
+
+class Airport(xsd.ComplexType):
+ type = xsd.Element(xsd.String)
+ code = xsd.Element(xsd.String)
+
+XML = """
+ IATA
+ WAW
+"""
+
+airport = Airport.parsexml(XML)
+print "Type:", airport.type
+print "Code:", airport.code
\ No newline at end of file
diff --git a/examples/xml_rendering.py b/examples/xml_rendering.py
new file mode 100644
index 00000000..dd6047ad
--- /dev/null
+++ b/examples/xml_rendering.py
@@ -0,0 +1,10 @@
+#Example 1. Rendering object to XML.
+from soapbox import xsd
+class Airport(xsd.ComplexType):
+ type = xsd.Element(xsd.String)
+ code = xsd.Element(xsd.String)
+
+airport = Airport()
+airport.type = "IATA"
+airport.code = "WAW"
+print airport.xml("takeoff_airport")
\ No newline at end of file
diff --git a/setup.py b/setup.py
new file mode 100644
index 00000000..4730be7f
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,23 @@
+from setuptools import setup
+
+setup(
+ name="soapbox",
+ version="0.1",
+ author="Damian Powazka",
+ author_email="dpowazka@gmail.com",
+ url="http://code.google.com/p/soapbox/",
+ description="",
+ long_description="",
+ download_url="",
+ license="New BSD",
+ install_requires=['lxml','jinja2'],
+ packages=["soapbox"],
+ platforms="Python 2.6 and later.",
+ classifiers=[
+ "Development Status :: 5 - Stable",
+ "License :: OSI Approved :: New BSD",
+ "Operating System :: OS Independent",
+ "Programming Language :: Python",
+ "Intended Audience :: Science/Research",
+ ]
+ )
\ No newline at end of file
diff --git a/soapbox/__init__.py b/soapbox/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/soapbox/py2wsdl.py b/soapbox/py2wsdl.py
new file mode 100644
index 00000000..363c415e
--- /dev/null
+++ b/soapbox/py2wsdl.py
@@ -0,0 +1,99 @@
+import sys
+import imp
+import wsdl
+from lxml import etree
+from py2xsd import generate_xsdspec
+
+def build_service(definitions, service):
+ wsdl_service = wsdl.Service()
+ for method in service.methods:
+ wsdl_port = wsdl.Port()
+ wsdl_port.name = method.operationName+"Port"
+ wsdl_port.binding = "tns:" + method.operationName+"Binding"
+ wsdl_port.address = wsdl.SOAP_Address(location=service.location)
+ wsdl_service.port = wsdl_port
+ definitions.services.append(wsdl_service)
+
+def build_bindings(definitions, service):
+ for method in service.methods:
+ binding = wsdl.Binding()
+ binding.name = method.operationName +"Binding"
+ binding.type = "tns:" + method.operationName + "PortType"
+ binding.binding = wsdl.SOAP_Binding()
+ binding.binding.style = "document"
+ binding.binding.transport = "http://schemas.xmlsoap.org/soap/http"
+
+ operation = wsdl.Operation()
+ operation.name = method.operationName
+ operation.operation = wsdl.SOAP_Operation()
+ operation.operation.soapAction = method.soapAction
+ operation.input = wsdl.Input(body=wsdl.SOAP_Body(use="literal"))
+ operation.output = wsdl.Input(body=wsdl.SOAP_Body(use="literal"))
+ binding.operation = operation
+
+ definitions.bindings.append(binding)
+
+
+def build_portTypes(definitions, service):
+ for method in service.methods:
+ portType = wsdl.PortType()
+ portType.name = method.operationName + "PortType"
+ operation = wsdl.Operation()
+ operation.name = method.operationName
+ operation.input = wsdl.Input(message="tns:" +method.operationName+"Input")
+ operation.output = wsdl.Input(message="tns:" +method.operationName+"Output")
+ portType.operation = operation
+
+ definitions.portTypes.append(portType)
+
+def build_messages(definitions, service):
+ for method in service.methods:
+ inputMessage = wsdl.Message(name=method.operationName+"Input")
+ inputMessage.part = wsdl.Part()
+ inputMessage.part.name = "body"
+ if isinstance(method.input, str):
+ inputMessage.part.element = "sns:"+method.input
+ else:
+ inputMessage.part.type = "sns:"+method.input.__name__.lower()
+ definitions.messages.append(inputMessage)
+
+ outputMessage = wsdl.Message(name=method.operationName+"Output")
+ outputMessage.part = wsdl.Part()
+ outputMessage.part.name = "body"
+ if isinstance(method.output, str):
+ outputMessage.part.element = "sns:"+method.output
+ else:
+ outputMessage.part.type = "sns:"+method.output.__name__.lower()
+ definitions.messages.append(outputMessage)
+
+def build_types(definitions, schema):
+ xsd_schema = generate_xsdspec(schema)
+ definitions.types = wsdl.Types(schema=xsd_schema)
+
+
+
+def generate_wsdl(service):
+ definitions = wsdl.Definitions(targetNamespace=service.targetNamespace)
+ build_types(definitions, service.schema)
+ build_service(definitions, service)
+ build_bindings(definitions, service)
+ build_portTypes(definitions, service)
+ build_messages(definitions, service)
+
+ xmlelement = etree.Element("{http://schemas.xmlsoap.org/wsdl/}definitions",
+ nsmap = {"xsd" : "http://www.w3.org/2001/XMLSchema",
+ "wsdl" : "http://schemas.xmlsoap.org/wsdl/",
+ "soap" : "http://schemas.xmlsoap.org/wsdl/soap/",
+ "sns" : service.schema.targetNamespace,
+ "tns" : service.targetNamespace})
+ definitions.render(xmlelement, definitions, "http://schemas.xmlsoap.org/wsdl/")
+ return etree.tostring(xmlelement, pretty_print=True)
+
+if __name__ == "__main__":
+ path = sys.argv[1]
+ globals = imp.load_source("", path)
+ service = getattr(globals,"SERVICE")
+ print generate_wsdl(service)
+
+
+
\ No newline at end of file
diff --git a/soapbox/py2xsd.py b/soapbox/py2xsd.py
new file mode 100644
index 00000000..ca40f355
--- /dev/null
+++ b/soapbox/py2xsd.py
@@ -0,0 +1,120 @@
+import sys
+import imp
+from lxml import etree
+import xsd
+import xsdspec
+from utils import uncapitalize
+
+def get_xsd_type(_type):
+ """Check is basic type from XSD scope, else it must be user
+ defined type."""
+ base_class = _type.__class__.__bases__[0]
+ if base_class == xsd.SimpleType or _type.__class__ == xsd.Long:
+ return "xsd:" + uncapitalize(_type.__class__.__name__)
+ else:
+ return "sns:" + uncapitalize(_type.__class__.__name__)
+
+def xsd_attribute(attribute):
+ xsdattr = xsdspec.Attribute()
+ xsdattr.name = attribute._name
+ xsdattr.use = attribute.use
+ xsdattr.type = get_xsd_type(attribute._type)
+ return xsdattr
+
+def create_xsd_element(element):
+ xsd_element = xsdspec.Element()
+ xsd_element.name = element._name
+ if element._minOccurs == 0:
+ xsd_element.minOccurs = 0
+ # SimpleType defined in place.
+ parent_type = element._type.__class__.__bases__[0]
+ if hasattr(element._type, "enumeration") and element._type.enumeration\
+ and parent_type == xsd.SimpleType:
+ xsd_element.simpleType = xsdspec.SimpleType()
+ xsd_element.simpleType.restriction = xsdspec.Restriction()
+ xsd_element.simpleType.restriction.base = get_xsd_type(element._type)
+ for value in element._type.enumeration:
+ enum = xsdspec.Enumeration.create(value)
+ xsd_element.simpleType.restriction.enumerations.append(enum)
+ else:
+ xsd_element.type = get_xsd_type(element._type)
+ return xsd_element
+
+def xsd_complexType(complexType):
+ xsd_ct = xsdspec.ComplexType()
+ xsd_ct.name = uncapitalize(complexType.__name__)
+
+ for attribute in complexType._meta.attributes:
+ xsd_attr = xsd_attribute(attribute)
+ xsd_ct.attributes.append(xsd_attr)
+
+ #Elements can be wrapped with few type of containers:
+ # sequence, all, choice or it can be a complexContent with
+ # extension or restriction.
+ if hasattr(complexType, "INDICATOR") and complexType.INDICATOR:
+ xsd_sequence = xsdspec.Sequence()
+ xsd_ct.sequence = xsd_sequence
+ container = xsd_sequence
+ else:
+ container = xsd_ct
+
+ for element in complexType._meta.fields:
+ xsd_element = create_xsd_element(element)
+ container.elements.append(xsd_element)
+ return xsd_ct
+
+def xsd_simpleType(st):
+ xsd_simpleType = xsdspec.SimpleType()
+ xsd_simpleType.name = st.__name__.lower()
+ xsd_restriction = xsdspec.Restriction()
+ xsd_restriction.base = get_xsd_type(st.__bases__[0]())
+ if hasattr(st,"enumeration") and st.enumeration:
+ for enum in st.enumeration:
+ xsd_restriction.enumerations.append(xsdspec.Enumeration.create(enum))
+ elif hasattr(st, "pattern") and st.pattern:
+ xsd_restriction.pattern = st.pattern
+ xsd_simpleType.restriction = xsd_restriction
+ return xsd_simpleType
+
+def generate_xsdspec(schema):
+ xsd_schema = xsdspec.Schema()
+ xsd_schema.targetNamespace = schema.targetNamespace
+ for st in schema.simpleTypes:
+ xsd_st = xsd_simpleType(st)
+ xsd_schema.simpleTypes.append(xsd_st)
+ for ct in schema.complexTypes:
+ xsd_ct = xsd_complexType(ct)
+ xsd_schema.complexTypes.append(xsd_ct)
+ generate_elements(xsd_schema, schema)
+ return xsd_schema
+
+def generate_elements(xsd_schema, schema):
+ for name, element in schema.elements.iteritems():
+ xsd_element = xsdspec.Element()
+ xsd_element.name = name
+ xsd_element.type = get_xsd_type(element._type)
+ xsd_schema.elements.append(xsd_element)
+
+
+def generate_xsd(schema):
+ xsd_schema = generate_xsdspec(schema)
+ xmlelement = etree.Element("{http://www.w3.org/2001/XMLSchema}schema",
+ nsmap = {"xsd" : "http://www.w3.org/2001/XMLSchema",
+ "sns" : schema.targetNamespace})
+ xsd_schema.render(xmlelement, xsd_schema)
+ return xmlelement
+
+
+if __name__ == "__main__":
+ import os
+ print os.getcwd()
+ if len(sys.argv) != 2:
+ print "Use: py2wsld.py "
+ sys.exit()
+ module = sys.argv[1]
+ globals = imp.load_source("module.name", module)
+ schema = getattr(globals,"Schema")
+ schemaelement = generate_xsd(schema)
+ print etree.tostring(schemaelement, pretty_print=True)
+
+
diff --git a/soapbox/soap.py b/soapbox/soap.py
new file mode 100644
index 00000000..95d21c81
--- /dev/null
+++ b/soapbox/soap.py
@@ -0,0 +1,208 @@
+#SOAP Protocol implementation, dispatchers and client stub.
+from lxml import etree
+import xsd
+import re
+import py2wsdl
+import httplib2
+
+class SOAPVersion:
+ SOAP11 = "SOAP 1.1"
+ SOAP12 = "SOAP 1.2"
+
+#SOAP messages description objects.
+class Header(xsd.ComplexType):
+ """SOAP Envelope Header."""
+ pass
+
+class Fault(xsd.ComplexType):
+ """SOAP Envelope Fault."""
+ faultcode = xsd.Element(xsd.String)
+ faultstring = xsd.Element(xsd.String)
+ detail = xsd.Element(xsd.String)
+
+class Body(xsd.ComplexType):
+ """SOAP Envelope Body."""
+ message = xsd.ClassNamedElement(xsd.ComplexType, minOccurs=0)
+ Fault = xsd.Element(Fault, minOccurs=0)
+ def content(self):
+ return etree.tostring(self._xmlelement[0], pretty_print=True)
+
+class Envelope(xsd.ComplexType):
+ """SOAP Envelope."""
+ Header = xsd.Element(Header, nilable=True)
+ Body = xsd.Element(Body)
+
+ @classmethod
+ def reponse(cls, return_object):
+ envelope = Envelope()
+ envelope.Body = Body()
+ envelope.Body.message = return_object
+ return envelope.xml("Envelope")
+
+Schema = xsd.Schema(
+ targetNamespace = "http://schemas.xmlsoap.org/soap/envelope/",
+ elementFormDefault = xsd.ElementFormDefault.QUALIFIED,
+ complexTypes = [Header, Body, Envelope])
+
+class SOAPError(Exception):
+ pass
+
+class Service(object):
+ """Describes service aggregating informations required for dispatching
+ and WSDL generation. """
+ def __init__(self, targetNamespace, location, schema, methods,
+ version=SOAPVersion.SOAP11):
+ """:param targetNamespace: string
+ :param location: string, endpoint url.
+ :param schema: xsd.Schema instance.
+ :param methods: list of xsd.Methods"""
+ self.targetNamespace = targetNamespace
+ self.location = location
+ self.schema = schema
+ self.methods = methods
+ self.version = version
+
+ def get_method(self, operationName):
+ return filter(lambda m:m.operationName ==operationName, self.methods)[0]
+
+
+#TODO:
+#1. Fault code could use Client.Authentication notation to indicate error type.
+
+def get_django_dispatch(service):
+ """Returns dispatch method for specified service. Dispatch method can be
+ pointed by urls.py, it will capture incoming SOAP message, translate it into
+ object and call appropriate method. Expecting return object that can be
+ translated to valid SOAP response for this service.
+ On any excpetion raised from method the response will be SOAP Fault message.
+ ValueError are translated into fault code Client, other to Server.
+ Incoming and outgoing XMLs are validated against XSD generated from service
+ schema. Incorrect or missing values will cause Fault response.
+ """
+ def get_soap_action(request):
+ """Finds soapAction information in HTTP header. First tries SOAP 1.1
+ soapAction and action header key, then looks into content type
+ for SOAP 1.2 action key. SOAP action is important for establishing
+ which method is called in document style calls where method name
+ is not wrapping the message content."""
+ if request.META.get("HTTP_SOAPACTION"):
+ return request.META.get("HTTP_SOAPACTION").replace('"','')
+ elif request.META.get("HTTP_ACTION"):
+ return request.META.get("HTTP_ACTION").replace('"','')
+ else:
+ content_types = request.META["CONTENT_TYPE"].split(";")
+ for content_type in content_types:
+ if content_type.strip(" ").startswith("action="):
+ return content_type.split("=")[1]
+ return None
+
+ def build_soap_message(o):
+ try:
+ o.xml(o.__class__.__name__.lower(), service.schema)#Validation.
+ except:
+ raise ValueError(e)
+
+ return Envelope.reponse(o)
+
+ def django_dispatch(request):
+ "Dispatch method tied to service."
+ #We don't want to import this in main context as the project may be
+ #using different way of dispatching. Django would be unnessesery
+ #dependecy which is sensible to assume to be true in Django dispatch only.
+ from django.http import HttpResponse
+ if request.method == "GET" and request.GET.has_key("wsdl"):
+ wsdl = py2wsdl.generate_wsdl(service)
+ return HttpResponse(wsdl,mimetype="text/xml")
+
+ try:
+ xml = request.raw_post_data
+ envelope = Envelope.parsexml(xml)
+ message = envelope.Body.content()
+ soap_action = get_soap_action(request)
+
+ for method in service.methods:
+ if soap_action != method.soapAction:
+ continue
+ if isinstance(method.input,str):
+ element = service.schema.elements[method.input]
+ input_object = element._type.parsexml(message,service.schema)
+ else:
+ input_object = method.input.parsexml(message,service.schema)
+ return_object = method.function(request, input_object)
+ return HttpResponse(build_soap_message(return_object))
+ raise ValueError("Method not found!")
+ except (ValueError,etree.XMLSyntaxError), e:
+ fault = Fault(faultcode="Client", faultstring=str(e), detail=str(e))
+ except Exception, e:
+ #Presents of detail element indicates that the problem is related
+ #to procesing Body element. See 4.4 SOAP Fault on
+ #http://www.w3.org/TR/2000/NOTE-SOAP-20000508/
+ fault = Fault(faultcode="Server", faultstring=str(e), detail=str(e))
+ envelope = Envelope()
+ envelope.Body = Body(Fault=fault)
+ return HttpResponse(envelope.xml("Envelope"))
+ return django_dispatch
+
+
+class Stub(object):
+ """Client stub. Handles only document style calls."""
+ SERVICE = None
+
+ def __init__(self, username=None, password=None):
+ self.username = username
+ self.password = password
+
+ def _build_header(self, method):
+ if self.SERVICE.version == SOAPVersion.SOAP11:
+ return {"content-type" : 'text/xml',
+ "SOAPAction" : method.soapAction}
+ elif self.SERVICE.version == SOAPVersion.SOAP12:
+ return {"content-type" : "application/soap+xml;action=%s" % method.soapAction}
+ else:
+ raise ValueError("SOAP Version not supported %s" % self.SERVICE.version)
+
+ def _handle_response(self, method, response, content):
+ envelope = Envelope.parsexml(content)
+ if envelope.Body.Fault:
+ raise SOAPError("Fault Code:%s, Fault String: %s" %
+ (envelope.Body.Fault.faultcode,
+ envelope.Body.Fault.faultstring))
+ message = envelope.Body.content()
+
+ if isinstance(method.output, str):
+ element = self.SERVICE.schema.elements[method.output]
+ _type = element._type
+ else:
+ _type = method.output
+
+ if self.SERVICE.schema:
+ return _type.parsexml(message, self.SERVICE.schema)
+ else:
+ return _type.parsexml(message)
+
+
+ def call(self, operationName, parameter):
+ #Will raise: lxml.etree.XMLSyntaxError on validation problems.
+ parameter.xml(parameter.__class__.__name__.lower(), self.SERVICE.schema)
+
+ h = httplib2.Http()
+ if self.username:
+ h.add_credentials(self.username, self.password)
+
+ method = self.SERVICE.get_method(operationName)
+ headers = self._build_header(method)
+ envelope = Envelope.reponse(parameter)
+
+ response, content = h.request(self.SERVICE.location, "POST",
+ body=envelope, headers=headers)
+ return self._handle_response(method, response, content)
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/soapbox/utils.py b/soapbox/utils.py
new file mode 100644
index 00000000..4760a78c
--- /dev/null
+++ b/soapbox/utils.py
@@ -0,0 +1,61 @@
+from urlparse import urlparse
+
+def removens(full_typename):
+ if full_typename is None:
+ return None
+
+ typename = full_typename.split(":")
+ if len(typename) == 2:
+ ns, typename = typename
+ else:
+ ns = None
+ return typename
+
+def classyfiy(value):
+ return value[0].upper() +value[1:]
+
+
+def get_get_type(XSD_NAMESPACES):
+ def get_type(full_typename):
+ if full_typename is None:
+ return None
+
+ typename = full_typename.split(":")
+ if len(typename) == 2:
+ ns, typename = typename
+ else:
+ ns = None
+ typename = typename[0]
+ if ns in XSD_NAMESPACES:
+ return "xsd." + classyfiy(typename)
+ else:
+ return classyfiy(typename)
+ return get_type
+
+def use(usevalue):
+ if usevalue == xsd.Use.OPTIONAL:
+ return "xsd.Use.OPTIONAL"
+ elif usevalue == xsd.Use.REQUIRED:
+ return "xsd.Use.REQUIRED"
+ elif usevalue == xsd.Use.PROHIBITED:
+ return "xsd.Use.PROHIBITED"
+ else:
+ raise ValueError
+
+def find_xsd_namepsace(nsmap):
+ namespaces = []
+ for key, value in nsmap.iteritems():
+ if value == "http://www.w3.org/2001/XMLSchema"\
+ or value == "http://www.w3.org/2000/10/XMLSchema":
+ namespaces.append(key)
+ return namespaces
+
+def urlcontext(url):
+ """http://polaris.flightdataservices.com/ws/ops-> ^ws/ops$"""
+ o = urlparse(url)
+ path = o.path[1:]#remove trailing /
+ return "^"+path+"$" #build regex
+
+def uncapitalize(value):
+ return value[0].lower() + value[1:]
+
diff --git a/soapbox/wsdl.py b/soapbox/wsdl.py
new file mode 100644
index 00000000..18b578e8
--- /dev/null
+++ b/soapbox/wsdl.py
@@ -0,0 +1,100 @@
+import xsd
+import xsdspec
+
+class SOAP_Binding(xsd.ComplexType):
+ NAMESPACE = "http://schemas.xmlsoap.org/wsdl/soap/"
+ ELEMENT_FORM_DEFAULT = xsd.ElementFormDefault.QUALIFIED
+ style = xsd.Attribute(xsd.String)
+ transport = xsd.Attribute(xsd.String)
+
+class SOAP_Operation(xsd.ComplexType):
+ NAMESPACE = "http://schemas.xmlsoap.org/wsdl/soap/"
+ ELEMENT_FORM_DEFAULT = xsd.ElementFormDefault.QUALIFIED
+ soapAction = xsd.Attribute(xsd.String)
+
+class SOAP_Body(xsd.ComplexType):
+ NAMESPACE = "http://schemas.xmlsoap.org/wsdl/soap/"
+ ELEMENT_FORM_DEFAULT = xsd.ElementFormDefault.QUALIFIED
+ use = xsd.Attribute(xsd.String)
+
+class SOAP_Address(xsd.ComplexType):
+ NAMESPACE = "http://schemas.xmlsoap.org/wsdl/soap/"
+ ELEMENT_FORM_DEFAULT = xsd.ElementFormDefault.QUALIFIED
+ location = xsd.Attribute(xsd.String)
+
+class Types(xsd.ComplexType):
+ schema = xsd.Element(xsdspec.Schema)
+
+class Part(xsd.ComplexType):
+ name = xsd.Attribute(xsd.String)
+ element = xsd.Attribute(xsd.String, use=xsd.Use.OPTIONAL)
+ type = xsd.Attribute(xsd.String, use=xsd.Use.OPTIONAL)
+
+class Message(xsd.ComplexType):
+ name = xsd.Attribute(xsd.String)
+ part = xsd.Element(Part)
+
+class Input(xsd.ComplexType):
+ message = xsd.Attribute(xsd.String, use=xsd.Use.OPTIONAL)
+ body = xsd.Element(SOAP_Body, minOccurs=0)
+
+class Operation(xsd.ComplexType):
+ name = xsd.Attribute(xsd.String)
+ input = xsd.Element(Input)
+ output = xsd.Element(Input)
+ body = xsd.Element(SOAP_Body)
+ operation = xsd.Element(SOAP_Operation)
+
+class PortType(xsd.ComplexType):
+ name = xsd.Attribute(xsd.String)
+ operation = xsd.Element(Operation)
+
+class Binding(xsd.ComplexType):
+ name = xsd.Attribute(xsd.String)
+ type = xsd.Attribute(xsd.String)
+ binding = xsd.Element(SOAP_Binding)
+ operation = xsd.Element(Operation)
+
+class Port(xsd.ComplexType):
+ name = xsd.Attribute(xsd.String)
+ binding = xsd.Attribute(xsd.String)
+ address = xsd.Element(SOAP_Address)
+
+class Service(xsd.ComplexType):
+ documentation = xsd.Element(xsd.String)
+ port = xsd.Element(Port)
+
+class Definitions(xsd.ComplexType):
+ targetNamespace = xsd.Attribute(xsd.String)
+ types = xsd.Element(Types)
+ messages = xsd.ListElement(Message,"message")
+ portTypes = xsd.ListElement(PortType, "portType")
+ bindings = xsd.ListElement(Binding, "binding")
+ services = xsd.ListElement(Service,"service")
+
+ @classmethod
+ def get_by_name(cls, _list, fullname):
+ name = fullname.split(":")[-1]
+ for item in _list:
+ if item.name == name:
+ return item
+ raise ValueError("Item '%s' not found in list:%s" % (name, _list))
+
+
+SCHEMA = xsd.Schema(
+ targetNamespace = "http://schemas.xmlsoap.org/wsdl/",
+ elementFormDefault = xsd.ElementFormDefault.QUALIFIED,
+ simpleTypes = [],
+ attributeGroups = [],
+ groups = [],
+ complexTypes = [Types, Part, Message, Input, Operation, PortType, Binding,
+ Port, Service, Definitions],
+ elements = {})
+
+
+
+
+
+
+
+
diff --git a/soapbox/wsdl2py.py b/soapbox/wsdl2py.py
new file mode 100644
index 00000000..ee5ff826
--- /dev/null
+++ b/soapbox/wsdl2py.py
@@ -0,0 +1,105 @@
+import sys
+from optparse import OptionParser
+from lxml import etree
+from jinja2 import Template,Environment
+from wsdl import Definitions
+from utils import removens, classyfiy, get_get_type, use, find_xsd_namepsace,urlcontext
+from xsd2py import TEMPLATE as SCHEMA_TEMPLATE
+
+environment = Environment()
+environment.filters["class"] = classyfiy
+environment.filters["removens"] = removens
+environment.filters["use"] = use
+environment.filters["urlcontext"] = urlcontext
+
+TEMPLATE = """#This was was generated by wsdl2py, try to not edit.
+from soapbox import soap
+{%- if schema %}
+{{schema}}
+{%- else %}
+from soapbox import xsd
+{%- endif %}
+
+{%- for service in definitions.services %}
+
+{%- set binding = definitions.get_by_name(definitions.bindings, service.port.binding) %}
+{%- set portType = definitions.get_by_name(definitions.portTypes, binding.type) %}
+{%- set inputMessage = definitions.get_by_name(definitions.messages, portType.operation.input.message) %}
+{%- set outputMessage = definitions.get_by_name(definitions.messages, portType.operation.output.message) %}
+
+{% if is_server %}
+def {{binding.operation.name}}({{inputMessage.part.element|removens}}):
+ #Put your implementation here.
+ return {{outputMessage.part.element|removens}}
+{%- endif %}
+
+{{binding.operation.name}}_method = xsd.Method(
+ {%- if is_server %}function = {{binding.operation.name}},{% endif %}
+ soapAction = "{{binding.operation.operation.soapAction}}",
+ {%- if inputMessage.part.element %}
+ input = "{{inputMessage.part.element|removens}}",#Pointer to Schema.elements
+ {%- else %}
+ input = {{inputMessage.part.type|removens|class}},
+ {%- endif %}
+ {%- if inputMessage.part.element %}
+ output = "{{outputMessage.part.element|removens}}",#Pointer to Schema.elements
+ {%- else %}
+ input = {{outputMessage.part.type|removens|class}},
+ {%- endif %}
+ operationName = "{{binding.operation.name}}")
+
+SERVICE = soap.Service(
+ targetNamespace = "{{definitions.targetNamespace}}",
+ location = "{{service.port.address.location}}",
+ schema = Schema,
+ methods = [{{binding.operation.name}}_method, ])
+
+{% if is_server %}
+#Comment this lines for py2xsd generation to avoid error message about
+#DJANGO_SETTINGS_MODULE not being set. If authorization is required
+#dispatch can be wrapped with login required in way similar to csrf_exempt.
+from django.views.decorators.csrf import csrf_exempt
+dispatch = csrf_exempt(soap.get_django_dispatch(SERVICE))
+
+#Put this lines in your urls.py:
+#urlpatterns += patterns('',
+# (r"{{service.port.address.location|urlcontext}}", ".dispatch")
+#)
+{%- else %}
+class ServiceStub(soap.Stub):
+ SERVICE = SERVICE
+
+ def {{binding.operation.name}}(self, {{inputMessage.part.element|removens}}):
+ return self.call("{{binding.operation.name}}", {{inputMessage.part.element|removens}})
+{%- endif %}
+{% endfor %}
+"""
+
+def main(is_server, path):
+ xml = open(path).read()
+ xmlelement = etree.fromstring(xml)
+ XSD_NAMESPACE = find_xsd_namepsace(xmlelement.nsmap)
+ environment.filters["type"] = get_get_type(XSD_NAMESPACE)
+ definitions = Definitions.parse_xmlelement(xmlelement)
+ schema = definitions.types.schema
+ schemaxml = environment.from_string(SCHEMA_TEMPLATE).render(schema=schema)
+ print environment.from_string(TEMPLATE).render(
+ definitions=definitions,
+ schema=schemaxml,
+ is_server=is_server)
+
+if __name__ == "__main__":
+ parser = OptionParser(usage = "usage: %prog [-c|-s] path_to_wsdl")
+ parser.add_option("-c", "--client", dest="client",
+ help="Generate webservice http client code.")
+ parser.add_option("-s", "--server", dest="server",
+ help="Generate webservice Django server code.")
+ (options, args) = parser.parse_args()
+ if options.client and options.server:
+ parser.error("Options -c and -s are mutually exclusive")
+ elif options.client:
+ main(False, options.client)
+ elif options.server:
+ main(True, options.server)
+ else:
+ parser.print_help()
\ No newline at end of file
diff --git a/soapbox/xsd.py b/soapbox/xsd.py
new file mode 100644
index 00000000..4765faa3
--- /dev/null
+++ b/soapbox/xsd.py
@@ -0,0 +1,668 @@
+from lxml import etree
+from datetime import datetime
+from copy import copy
+#http://lxml.de/validation.html#xmlschema
+
+#Design Decision Log:
+#0. I have decided to not use dews/dexml approach to field description
+# as it doesn't give good distinction between element and attribute.
+# It is not a problem when parsing a XML, but it is quite important
+# for rendering and XSD generation. The new syntax will look like:
+# tail_number = xsd.Attribute(xsd.String)
+# flight_number = xsd.Element(xsd.Interger)
+# which makes this distinction clear.
+#
+#1. render will take value/instance as parameter
+# More obvious would be if render just rendered current object,
+# but this approach doesn't work with Python simple types like string.
+# Where you can not call "x".render() so type method render must
+# take a value as a parameter, which may same odd for complex types.
+#
+#2. Due to render taking a value as parameter it could be implemented
+# as a static/class method, but it is not.
+# xsd.Element takes a class or an instance, but if class was passed
+# it will create an instance - so parameterless constructor is required
+# Reason for that is to keep API consistent. There are two syntaxes
+# a) xsd.Element(xsd.String)
+# b) xsd.Element(xsd.String(enumeration=["A","B"])
+# Because instance if required in case b) creating it from class in case
+# a) makes other methods independent from this two syntaxes.
+
+
+class Use:
+ OPTIONAL = "optional"
+ REQUIRED = "required"
+ PROHIBITED = "prohibited"
+
+class Inheritance:
+ RESTRICTION = "RESTRICTION"
+ EXTENSION = "EXTENSION"
+
+class ElementFormDefault:
+ QUALIFIED = "qualified"
+ UNQUALIFIED = "unqualified"
+
+class Type(object):
+ """Abstract."""
+ def accept(self, value):
+ raise NotImplementedError
+
+ def parse_xmlelement(self, xmlelement):
+ raise NotImplementedError
+
+ def parsexml(self, xml):
+ raise NotImplementedError
+
+ def rander(self, parent, value):
+ raise NotImplementedError
+
+class SimpleType(Type):
+ """Defines an interface for simple types."""
+ def render(self, parent, value, namespace):
+ parent.text = self.xmlvalue(value)
+
+ def parse_xmlelement(self, xmlelement):
+ return self.pythonvalue(xmlelement.text)
+
+ def xmlvalue(self, value):
+ raise NotImplementedError
+
+ def pythonvalue(self, xmlavalue):
+ raise NotImplementedError
+
+
+class String(SimpleType):
+ enumeration = None#To be defined in child.
+ def __init__(self, enumeration=None):
+ if enumeration:
+ self.enumeration = enumeration
+
+ def accept(self,value):
+ if value is None:
+ return value
+ if not isinstance(value,str):
+ raise ValueError("Value '%s' for class '%s'." % (str(value),self.__class__.__name__))
+ if self.enumeration:
+ if value in self.enumeration:
+ return value
+ else:
+ raise ValueError("Value '%s' not in list %s." % (str(value), self.enumeration))
+ else:
+ return value
+
+ def xmlvalue(self, value):
+ return value
+
+ def pythonvalue(self, xmlvalue):
+ return xmlvalue
+
+class Boolean(SimpleType):
+ def accept(self, value):
+ if value in [True, False, None]:
+ return value
+ else:
+ raise ValueError("Value '%s' for class '%s'." % (str(value),self.__class__.__name__))
+
+ def xmlvalue(self, value):
+ if value == True:
+ return "true"
+ elif value == False:
+ return "false"
+ elif value is None:
+ return "nil"
+ else:
+ raise ValueError("Value '%s' for class '%s'." % (str(value),self.__class__.__name__))
+
+ def pythonvalue(self,value):
+ if value == 'false':
+ return False
+ elif value == 'true':
+ return True
+ elif value == 'nil' or value is None:
+ return None
+ else:
+ raise ValueError
+
+class DateTime(SimpleType):
+ """Example text value: 2001-10-26T21:32:52"""
+ FORMTA = "%Y-%m-%dT%H:%M:%S"
+ def accept(self, value):
+ if value is None:
+ return None
+ elif isinstance(value, datetime):
+ return value
+ elif isinstance(value, str):
+ return datetime.strptime(value, self.FORMTA)
+ raise ValueError("Incorrect type value '%s' for Datetime field." % value)
+
+ def xmlvalue(self, value):
+ if value is None:
+ return "nil"
+ else:
+ return value.strftime(self.FORMTA)
+
+ def pythonvalue(self, value):
+ if value is None or value == 'nil':
+ return None
+ else:
+ return datetime.strptime(value, self.FORMTA)
+
+class Integer(SimpleType):
+ def __init__(self, enumeration = None, fractionDigits=None, maxExclusive=None,
+ maxInclusive=None, minExclusive=None, minInclusive=None,
+ pattern=None, totalDigits=None):
+ pass
+
+ def accept(self, value):
+ if value is None:
+ return None
+ elif isinstance(value, int):
+ return value
+ elif isinstance(value, str):
+ return int(value)
+ else:
+ raise ValueError("Incorrect value '%s' for Interger field." % value)
+
+ def xmlvalue(self, value):
+ return str(value)
+
+ def pythonvalue(self, xmlvalue):
+ if xmlvalue == 'nil':
+ return None
+ else:
+ return self.accept(xmlvalue)
+
+class Long(Integer):
+ def accept(self, value):
+ value = super(Long, self).accept(value)
+ if value is None:
+ return None
+ else:
+ if -9223372036854775808 < value < 9223372036854775807:
+ return value
+ else:
+ raise ValueError("Value '%s' out of range for Long type: -9223372036854775808 and 9223372036854775807.")
+
+
+
+
+class Element(object):
+ """Basic building block, represents a XML element that can appear one or zero
+ times in XML that should be rendered as subelement e.g.
+ LN-KKY
+ Tail number is element.
+ For elements that can appear multiple times use ListElement."""
+ _creation_counter = 0
+
+ def __init__(self, _type, minOccurs = 1, tagname = None, nilable = False,
+ default = None):
+ """:param _type: Class or instance of class that inherits from Type,
+ usually a child of SimpleType from xsd package,
+ or user defined class that inherits from ComplexType.
+ :param minOccurs: int, how many times this object can appear in valid XML
+ can be 0 or 1. See: difference between Element and
+ ListElement.
+ :param tagname: str, name of tag when different to field declared in
+ ComplexType, important when tag name is python reserved
+ work e.g. import
+ :param nilable: bool, is object nilable.
+ """
+ if not minOccurs in [0,1]: raise "minOccurs for Element can by only 0 or 1, use ListElement insted."
+ self._creation_number = Element._creation_counter
+ Element._creation_counter += 1
+ if isinstance(_type, Type):
+ self._type = _type
+ else:
+ self._type = _type()
+ self._minOccurs = minOccurs
+ self.tagname = tagname
+ self.default = default
+
+ def empty_value(self):
+ """Empty value methods is used when new object is constructed for
+ field initialization in most cases this should be None, but for lists
+ and other types of aggregates this should by an empty aggregate."""
+ return self.default
+
+ def accept(self,value):
+ """Checks is the value correct from type defined in constructions."""
+ return self._type.accept(value)
+
+ def render(self, parent, field_name, value, namespace=None):
+ if value is None:
+ return
+ #This allows complexType to redefine the name space a.k.a.
+ #to use name space different then parent's one.
+ if hasattr(self._type,"NAMESPACE"):
+ namespace = self._type.NAMESPACE
+ if namespace and self._type.ELEMENT_FORM_DEFAULT == ElementFormDefault.QUALIFIED :
+ field_name = "{%s}%s" % (namespace, field_name)
+ xmlelement = etree.Element(field_name)
+ self._type.render(xmlelement, value, namespace)
+ parent.append(xmlelement)
+
+
+ def parse(self, instance, field_name, xmlelement):
+ value = self._type.parse_xmlelement(xmlelement)
+ setattr(instance, field_name, value)
+
+ def __repr__(self):
+ return "%s<%s>" % (self.__class__.__name__,self._type.__class__.__name__)
+
+class ClassNamedElement(Element):
+ """Use this element when tagname should be based on class name in rendering time."""
+ def __init__(self,_type, minOccurs = 1, nilable = False):
+ super(ClassNamedElement, self).__init__(_type, minOccurs,None,nilable)
+
+ def render(self, parent, field_name, value, namespace=None):
+ if value is None:
+ return
+ if hasattr(value,"NAMESPACE"):
+ namespace = value.NAMESPACE
+
+ if namespace:
+ tagname = "{%s}%s" % (namespace, value.__class__.__name__.lower())
+ else:
+ tagname = value.__class__.__name__
+ xmlelement = etree.Element(tagname)
+ self._type.render(xmlelement, value)
+ parent.append(xmlelement)
+
+
+class Attribute(Element):
+ """Represents a field that is a XML attribute. e.g.
+
+ Programmer
+
+ name and surname are attributes. Attribute type can be only simple types."""
+ def __init__(self,type_clazz, use=Use.REQUIRED, tagname = None,nilable = False,
+ default=None):
+ """
+ :param type_clazz: Only simple tapes are accepted: String, Integer etc.
+ """
+ if use == Use.REQUIRED:
+ minOccurs = 1
+ else:
+ minOccurs = 0
+ super(Attribute, self).__init__(type_clazz, tagname=tagname, minOccurs = minOccurs)
+ self.nilable = nilable
+ self.use = use
+ self.default = default
+
+ def render(self, parent, field_name, value, namespace=None):
+ if value is None:
+ if self._minOccurs:
+ raise ValueError("Value None is not acceptable for required field.")
+ elif not self.nilable:
+ return
+ xmlvalue = self._type.xmlvalue(value)
+ parent.set(field_name, xmlvalue)
+
+ def parse(self, instance, field_name, xmlelement):
+ xmlvalue = xmlelement.get(field_name)
+ if xmlvalue is None:
+ xmlvalue = self.default
+ value = self._type.pythonvalue(xmlvalue)
+ setattr(instance, field_name, value)
+
+
+class Ref(Element):
+ """References are not fields, they point to type that has them - usually groups.
+ With Ref fields will be rendered directly into parent object. e.g.
+ class Person(xsd.Group):
+ name = xsd.Element(xsd.String)
+ surname = xsd.Element(xsd.String)
+ class Job(xsd.ComplexType):
+ title = xsd.Element(xsd.String)
+ person = xsd.Ref(Person)
+ The valid XML will be:
+
+ Programmer
+ An
+ Brown
+
+ Note that name and surname are not wrapped with tag.
+ """
+ def empty_value(self):
+ return copy(self._type)
+
+ def render(self, parent, field_name, value, namespace=None):
+ if value is None:
+ if self._required:
+ raise ValueError("Value None is not acceptable for required field.")
+ else:
+ return
+ self._type.render(parent, value, namespace)
+
+class Content(Ref):
+ """Direct access to element.text. Note that <> will be escaped."""
+ def empty_value(self):
+ return None
+
+
+class ListElement(Element):
+ """Tag element that can appear many times in valid XML. e.g.
+
+ G-ABCD
+ John Backus
+ Kent Beck
+ Larry Breed
+
+ passenger is an example of ListElement, the definition would look:
+ passengers = xsd.ListElement(xsd.String, "passenger")
+ Note that tag name is required for this field, as the field name should
+ be in plural form, and tag usually is not.
+ """
+ def __init__(self, clazz, tagname, minOccurs=None,maxOccurs=None):
+ super(ListElement,self).__init__(clazz)
+ self.minOccurs = minOccurs
+ self.maxOccurs = maxOccurs
+ self.tagname = tagname
+
+ def accept(self,value):
+ return value
+
+ def empty_value(this):
+ class TypedList(list):
+ def append(self,value):
+ accepted_value = this._type.accept(value)
+ super(TypedList,self).append(accepted_value)
+ return TypedList()
+
+
+ def render(self, parent, field_name, value, namespace=None):
+ items = value#The value must be list of items.
+ if self.minOccurs and len(items) < self.minOccurs:
+ raise ValueError("For %s minOccurs=%d but list length %d." %(name, self.minOccurs, len(items)))
+ if self.maxOccurs and len(items) > self.maxOccurs:
+ raise ValueError("For %s maxOccurs=%d but list length %d." % (name, self.maxOccurs))
+
+ for item in items:
+ if namespace:
+ tagname = "{%s}%s" % (namespace,self.tagname)
+ else:
+ tagname = self.tagname
+ xmlelement = etree.Element(tagname)
+ self._type.render(xmlelement, item, namespace)
+ parent.append(xmlelement)
+
+ def parse(self, instance, field_name, xmlelement):
+ value = self._type.parse_xmlelement(xmlelement)
+ _list = getattr(instance, field_name)
+ _list.append(value)
+
+class ComplexTypeMetaInfo(object):
+ def __init__(self,cls):
+ self.cls = cls
+ self.fields = []
+ self.attributes = []
+ self.groups = []
+ for attr in dir(cls):
+ item = getattr(cls,attr)
+ if isinstance(getattr(cls,attr),Attribute):
+ item._name = attr
+ self.attributes.append(item)
+ elif isinstance(item, Ref):
+ item._name = attr
+ self.groups.append(item)
+ elif isinstance(item, Element):
+ item._name = attr
+ self.fields.append(item)
+ self.fields = sorted(self.fields, key=lambda f: f._creation_number)
+ self.attributes = sorted(self.attributes, key=lambda f: f._creation_number)
+ self.groups = sorted(self.groups, key=lambda f: f._creation_number)
+ self.allelements = sorted(self.fields+self.groups, key=lambda f: f._creation_number)
+ self.all = sorted(self.fields+self.groups+self.attributes, key=lambda f: f._creation_number)
+
+class Complex_PythonType(type):
+ """Python type for ComplexType, builds _meta object for every class that
+ inherit from ComplexType. """
+ def __new__(cls,name,bases,attrs):
+ newcls = super(Complex_PythonType,cls).__new__(cls,name,bases,attrs)
+ if name != 'Complex':
+ newcls._meta = ComplexTypeMetaInfo(newcls)
+ return newcls
+
+class ComplexType(Type):
+ """Parent for XML elements that have sub-elements."""
+ INDICATOR = None#Indicator see: class Indicators. To be defined in sub-type.
+ INHERITANCE = None#Type of inheritance see: class Inheritance, to be defined in sub-type.
+ NAMESPACE = None#String, preferably URL with name space for this element. Is set be Scheme instance.
+ ELEMENT_FORM_DEFAULT = None#String, one of two values.
+
+ __metaclass__ = Complex_PythonType
+
+ def __new__(cls,*args,**kwargs):
+ instance = super(ComplexType,cls).__new__(cls)
+ for field in instance._meta.all:
+ setattr(instance,field._name,field.empty_value())
+ return instance
+
+ def __init__(self,**kwargs):
+ for key,value in kwargs.items():
+ setattr(self,key,value)
+
+ def __setattr__(self, attr, value):
+ if attr == "_xmlelement":
+ super(ComplexType,self).__setattr__(attr,value)
+ else:
+ try:
+ field = self._find_field(self._meta.all, attr)
+ except IndexError:
+ raise AttributeError("Model '%s' doesn't have attribute '%s'." % (self.__class__.__name__,attr))
+ super(ComplexType,self).__setattr__(attr,field.accept(value))
+
+ def accept(self, value):
+ """Instance methods that valid other instance."""
+ if value is None:
+ return None
+ elif isinstance(value,self.__class__):
+ return value
+ else:
+ raise ValueError('!!')
+
+
+ def render(self, parent, instance, namespace=None):
+ if instance is None: return None
+ for field in instance._meta.all:
+ if self.ELEMENT_FORM_DEFAULT == ElementFormDefault.QUALIFIED:
+ field.render(
+ parent = parent,
+ field_name = field._name,
+ value = getattr(instance, field._name),
+ namespace = self.NAMESPACE)
+ else:
+ field.render(
+ parent = parent,
+ field_name = field._name,
+ value = getattr(instance, field._name))
+
+ @classmethod
+ def _find_field(cls, fields, name):
+ return filter(lambda f:f._name == name,fields)[0]
+
+ @classmethod
+ def _get_field_by_name(cls, fields, field_name):
+ for field in fields:
+ if field.tagname == field_name or field._name == field_name:
+ return field
+ raise ValueError("Field not found '%s', fields: %s" %(field_name, fields))
+
+
+ @classmethod
+ def _find_subelement(cls, field, xmlelement):
+ def gettagns(tag):
+ """Translates tag string in format {namespace}
+ tag to tuple (namespace,tag)."""
+ if tag[:1] == "{":
+ return tag[1:].split("}", 1)
+ else:
+ return (None,tag)
+ #--------------------------------------------
+ subelements = []
+ for subelement in xmlelement:
+ if isinstance(subelement, etree._Comment):
+ continue
+ ns, tag = gettagns(subelement.tag)
+ if tag == field._name or tag == field.tagname:
+ subelements.append(subelement)
+ return subelements
+
+ @classmethod
+ def parse_xmlelement(cls, xmlelement):
+ instance = cls()
+ instance._xmlelement = xmlelement
+ for attribute in instance._meta.attributes:
+ attribute.parse(instance, attribute._name, xmlelement)
+
+ for field in instance._meta.fields:
+ subelements = cls._find_subelement(field, xmlelement)
+ for subelement in subelements:
+ field.parse(instance, field._name, subelement)
+
+ for group in instance._meta.groups:
+ group.parse(instance, group._name, xmlelement)
+
+ return instance
+
+ @classmethod
+ def __parse_with_validation(cls, xml, schema):
+ from py2xsd import generate_xsd
+ schemaelement = etree.XMLSchema(generate_xsd(schema))
+ parser = etree.XMLParser(schema = schemaelement)
+ xmlelement = etree.fromstring(xml, parser)
+ return xmlelement
+
+ @classmethod
+ def parsexml(cls, xml, schema=None):
+ if schema:
+ xmlelement = cls.__parse_with_validation(xml, schema)
+ else:
+ xmlelement = etree.fromstring(xml)
+ return cls.parse_xmlelement(xmlelement)
+
+ def xml(self, tagname, schema=None):
+ if self.NAMESPACE:
+ tagname = "{%s}%s" % (self.NAMESPACE, tagname)
+ xmlelement = etree.Element(tagname)
+ self.render(xmlelement, self, self.NAMESPACE)
+ xml = etree.tostring(xmlelement, pretty_print=True)
+ if schema:
+ self.__parse_with_validation(xml, schema)
+ return xml
+
+class Group(ComplexType):
+ """Parent object for XSD Groups. Marker. Must be use with Ref."""
+ pass
+
+class AttributeGroup(Group):
+ """Parent object for XSD Attribute Groups. Marker. Must be use with Ref."""
+ pass
+
+class Document(ComplexType):
+ """Represents whole xml, is expected to have only one field the root."""
+ class MockElement(object):
+ def __init__(self):
+ self.element = None
+ def append(self, element):
+ self.element = element
+
+ def render(self):
+ field = self._meta.fields[0]#The only field
+ mockelement = Document.MockElement()
+ instance = getattr(self, field._name)
+ field.render(mockelement, field._name, instance, self.NAMESPACE)
+ return etree.tostring(mockelement.element, pretty_print=True)
+
+ #TODO:schema support
+ @classmethod
+ def parsexml(cls, xml):
+ field = self._meta.fields[0]#The only field
+ xmlelement = etree.fromstring(xml)
+ field.parse(self, field._name, xmlelement)
+
+
+
+class Indicator(object):
+ def __init__(self, fields):
+ self.fields = fields
+
+class Sequence(Indicator):
+ pass
+
+class Choice(Indicator):
+ pass
+
+class All(Indicator):
+ pass
+
+class List(SimpleType):
+ pass
+
+class AnyURI(String):
+ pass
+
+class QName(String):
+ pass
+
+class NMTOKEN(String):
+ pass
+
+class NMTOKENS(String):
+ pass
+
+
+class Schema(object):
+ """Main object for XSD schema. This object is required for XSD and WSDLgeneration
+ and correct namespaces as it propagates targetNamespace to all objects.
+ Instance of this is expected to be named Schema. """
+ def __init__(self,targetNamespace, elementFormDefault=ElementFormDefault.UNQUALIFIED ,simpleTypes=[], attributeGroups=[], groups=[], complexTypes=[], elements={}):
+ """
+ :param targetNamespace: string, xsd namespace URL.
+ :param elementFormDefault: unqualified/qualified Defines should namespace
+ be used in child elements. Suggested: qualified. Default: unqualified as
+ it is default in XSD.
+ :param simpleTypes: List of objects that extend xsd.SimpleType.
+ :param attributeGroups: List of objects that extend xsd.AttributeGroup.
+ :param groups: List of objects that extend xsd.Group.
+ :param complexTypes: List of complexTypes class.
+ :param elements: dict of xsd.Elements that are direct schema elements.
+ """
+ self.targetNamespace = targetNamespace
+ self.elementFormDefault = elementFormDefault
+ self.simpleTypes = simpleTypes
+ self.attributeGroups = attributeGroups
+ self.groups = groups
+ self.complexTypes = complexTypes
+ self.elements = elements
+
+ self.__init_namespace(self.simpleTypes)
+ self.__init_namespace(self.groups)
+ self.__init_namespace(self.attributeGroups)
+ self.__init_namespace(self.complexTypes)
+
+ def __init_namespace(self, types):
+ for _type in types:
+ _type.NAMESPACE = self.targetNamespace
+ _type.ELEMENT_FORM_DEFAULT = self.elementFormDefault
+
+
+class Method(object):
+ """Method description. The main information is mapping soapAction and operationName
+ to function for dispatcher. input and output mapping informs how and which
+ objects should be created on incoming/outgoing messages."""
+ def __init__(self, operationName, soapAction, input, output, function=None):
+ """:param function: The function that should be called. Required only for server."""
+ self.operationName = operationName
+ self.soapAction = soapAction
+ self.input = input
+ self.output = output
+ self.function = function
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/soapbox/xsd2py.py b/soapbox/xsd2py.py
new file mode 100644
index 00000000..223d241d
--- /dev/null
+++ b/soapbox/xsd2py.py
@@ -0,0 +1,133 @@
+import sys
+from jinja2 import Template,Environment
+from xsdspec import *
+from utils import removens, classyfiy, get_get_type, use, find_xsd_namepsace
+
+
+environment = Environment()
+environment.filters["class"] = classyfiy
+environment.filters["removens"] = removens
+environment.filters["use"] = use
+
+
+TEMPLATE = """from soapbox import xsd
+{# ------------------ SimpleType Generation ---------------------#}
+{% for st in schema.simpleTypes %}
+ {%- if st.restriction %}
+class {{st.name|class}}({{st.restriction.base|type}}):
+ {%- if st.restriction.enumerations %}
+ enumeration = [{% for enum in st.restriction.enumerations %} "{{enum.value}}", {% endfor %}]
+ {%- elif st.restriction.pattern %}
+ pattern = r"{{st.restriction.pattern.value}}"
+ {%- endif %}
+ {% endif %}
+
+ {%- if st.list %}
+class {{st.name|class}}(xsd.List):
+ pass
+ {%- endif %}
+{%- endfor %}
+{# ---------------End of SimpleType Generation -----------------#}
+
+{# ------------------------- GROUOPS ----------------------------------------#}
+{%- for attrGroup in schema.attributeGroups %}
+class {{attrGroup.name|class}}(xsd.AttributeGroup):
+ {%- for attribute in attrGroup.attributes %}
+ {{attribute.name}} = xsd.Attribute({{attribute.type|type}}{% if attribute.use %}, use={{attribute.use|use}}{% endif %})
+ {%- endfor %}
+{% endfor %}
+
+{%- for group in schema.groups %}
+class {{group.name|class}}(xsd.Group):
+ {%- for element in group.sequence.elements %}
+ {%- if element.ref %}
+ {{element.ref|removens}} = xsd.Element({{element.ref|type}})
+ {%- else %}
+ {{element.name}} = xsd.Element({{element.type|type}})
+ {%- endif %}
+ {%- endfor %}
+{% endfor %}
+
+{# ---------------------------------------------------------------------------#}
+
+{# -------------------------- ComplexTypes -----------------------------------#}
+{% for ct in schema.complexTypes %}
+{% set content = ct %}
+
+{%- if not ct.sequence and not ct.complexContent %}
+class {{ct.name|class}}(xsd.ComplexType):
+{%- endif %}
+
+{%- if ct.complexContent %}
+ {%- if ct.complexContent.restriction %}
+class {{ct.name|class}}({{ct.complexContent.restriction.base|type}}):
+ INHERITANCE = xsd.Inheritance.RESTRICTION
+ {%- set content = ct.complexContent.restriction %}
+ {%- else %}
+class {{ct.name|class}}({{ct.complexContent.extension.base|type}}):
+ INHERITANCE = xsd.Inheritance.EXTENSION
+ {%- set content = ct.complexContent.extension %}
+ {%- endif %}
+{%- elif ct.sequence %}
+class {{ct.name|class}}(xsd.ComplexType):
+ INHERITANCE = None
+ {%- set content = ct %}
+{%- endif %}
+
+{%- if content.sequence %}
+ INDICATOR = xsd.Sequence
+ {%- set elements = content.sequence.elements %}
+{%- elif content.all %}
+ INDICATOR = xsd.All
+ {%- set elements = content.all.elements %}
+{%- elif content.choice %}
+ INDICATOR = xsd.Choice
+ {%- set elements = content.choice.elements %}
+{%- endif %}
+
+{%- for attribute in content.attributes %}
+ {%- if attribute.ref %}
+ {{attribute.ref|removens}} = xsd.Attribute({{attribute.ref|type}})
+ {%- else %}
+ {{attribute.name}} = xsd.Attribute({{attribute.type|type}}{% if attribute.use %}, use={{attribute.use|use}}{% endif %})
+ {%- endif %}
+{%- endfor %}
+
+{%- for attrGroupRef in content.attributeGroups %}
+ {{attrGroupRef.ref|removens}} = xsd.Ref({{attrGroupRef.ref|type}})
+{%- endfor %}
+
+{%- for element in elements %}
+ {%- if element.type %}
+ {{element.name}} = xsd.Element({{element.type|type}}{% if element.minOccurs == 0 %}, minOccurs=0{% endif %})
+ {%- endif %}
+ {%- if element.simpleType %}
+ {{element.name}} = xsd.Element({{element.simpleType.restriction.base|type}}( enumeration =
+ [{% for enum in element.simpleType.restriction.enumerations %} "{{enum.value}}",{% endfor %}]) )
+ {%- endif %}
+ {%- if element.ref %}
+ {{element.ref|removens}} = xsd.Ref({{element.ref|type}})
+ {%- endif %}
+{%- endfor %}
+{% endfor %}
+{# ------------------------ End of ComplexTypes -------------------------------#}
+
+Schema = xsd.Schema(
+ targetNamespace = "{{schema.targetNamespace}}",
+ elementFormDefault = "{{schema.elementFormDefault}}",
+ simpleTypes = [{% for st in schema.simpleTypes %} {{st.name|class}},{% endfor %}],
+ attributeGroups = [{% for ag in schema.attributeGroups %} {{ag.name|class}},{% endfor %}],
+ groups = [{% for g in schema.groups %} {{g.name|class}},{% endfor %}],
+ complexTypes = [{% for ct in schema.complexTypes %} {{ct.name|class}},{% endfor %}],
+ elements = { {% for e in schema.elements %} "{{e.name}}":xsd.Element({{e.type|type}}),{% endfor %}})
+"""
+
+XSD_NAMESPACE = None
+
+if __name__ == "__main__":
+ xml = open(sys.argv[1]).read()
+ xmlelement = etree.fromstring(xml)
+ XSD_NAMESPACE = find_xsd_namepsace(xmlelement.nsmap)
+ environment.filters["type"] = get_get_type(XSD_NAMESPACE)
+ schema = Schema.parse_xmlelement(etree.fromstring(xml))
+ print environment.from_string(TEMPLATE).render(schema=schema)
\ No newline at end of file
diff --git a/soapbox/xsdspec.py b/soapbox/xsdspec.py
new file mode 100644
index 00000000..25f27cad
--- /dev/null
+++ b/soapbox/xsdspec.py
@@ -0,0 +1,128 @@
+from lxml import etree
+import xsd
+
+class Enumeration(xsd.ComplexType):
+ NAMESPACE = "http://www.w3.org/2001/XMLSchema"
+ value = xsd.Attribute(xsd.String)
+
+ @classmethod
+ def create(cls, value):
+ enum= Enumeration()
+ enum.value = value
+ return enum
+
+class Pattern(xsd.ComplexType):
+ NAMESPACE = "http://www.w3.org/2001/XMLSchema"
+ value = xsd.Attribute(xsd.String)
+
+class Restriction(xsd.ComplexType):
+ NAMESPACE = "http://www.w3.org/2001/XMLSchema"
+ base = xsd.Attribute(xsd.String)
+ enumerations = xsd.ListElement(Enumeration,"enumeration")
+ pattern = xsd.Element(Pattern)
+
+ def to_python(self):
+ enum_values = map(lambda e: '"%s"'%e.value, self.enumerations)
+ return "[%s]" % ",".join(enum_values)
+
+class List(xsd.ComplexType):
+ NAMESPACE = "http://www.w3.org/2001/XMLSchema"
+ pass
+
+class SimpleType(xsd.ComplexType):
+ NAMESPACE = "http://www.w3.org/2001/XMLSchema"
+ name = xsd.Attribute(xsd.String, use=xsd.Use.OPTIONAL)
+ restriction = xsd.Element(Restriction,minOccurs=0)
+ list = xsd.Element(List,minOccurs=0)
+
+
+class Element(xsd.ComplexType):
+ NAMESPACE = "http://www.w3.org/2001/XMLSchema"
+ name = xsd.Attribute(xsd.String, use=xsd.Use.OPTIONAL)
+ type = xsd.Attribute(xsd.String, use=xsd.Use.OPTIONAL)
+ ref = xsd.Attribute(xsd.String, use=xsd.Use.OPTIONAL)
+ minOccurs = xsd.Attribute(xsd.Integer, use=xsd.Use.OPTIONAL)
+ simpleType = xsd.Element(SimpleType, minOccurs=0)
+
+
+class Sequence(xsd.ComplexType):
+ NAMESPACE = "http://www.w3.org/2001/XMLSchema"
+ elements = xsd.ListElement(Element, "element")
+
+
+class Attribute(xsd.ComplexType):
+ NAMESPACE = "http://www.w3.org/2001/XMLSchema"
+ name = xsd.Attribute(xsd.String)
+ ref = xsd.Attribute(xsd.String)
+ type = xsd.Attribute(xsd.String)
+ use = xsd.Attribute(xsd.String)
+
+
+class AttributeGroup(xsd.ComplexType):
+ NAMESPACE = "http://www.w3.org/2001/XMLSchema"
+ name = xsd.Attribute(xsd.String)
+ attributes = xsd.ListElement(Attribute, "attribute")
+
+class AttributeGroupReference(xsd.ComplexType):
+ NAMESPACE = "http://www.w3.org/2001/XMLSchema"
+ ref = xsd.Attribute(xsd.String)
+
+ def to_python(self):
+ typename = get_type(self.ref)
+ data = {"name": typename.lower(), "type" : typename}
+ return """ %(name)s = xsd.Ref(%(type)s)\n""" % data
+
+class Extension(xsd.ComplexType):
+ NAMESPACE = "http://www.w3.org/2001/XMLSchema"
+ base = xsd.Attribute(xsd.String)
+ sequence = xsd.Element(Sequence)
+ attributes = xsd.ListElement(Attribute, "attribute")
+ attributeGroups = xsd.ListElement(AttributeGroupReference,"attributeGroup")
+
+class ComplexContent(xsd.ComplexType):
+ NAMESPACE = "http://www.w3.org/2001/XMLSchema"
+ mixed = xsd.Attribute(xsd.Boolean)
+ extension = xsd.Element(Extension)
+ restriction = xsd.Element(Extension)
+
+
+class ComplexType(xsd.ComplexType):
+ NAMESPACE = "http://www.w3.org/2001/XMLSchema"
+ name = xsd.Attribute(xsd.String)
+ sequence = xsd.Element(Sequence)
+ all = xsd.Element(Sequence)
+ complexContent = xsd.Element(ComplexContent)
+ attributes = xsd.ListElement(Attribute,"attribute")
+ attributeGroups = xsd.ListElement(AttributeGroupReference, "attributeGroup")
+
+class Group(xsd.ComplexType):
+ NAMESPACE = "http://www.w3.org/2001/XMLSchema"
+ name = xsd.Attribute(xsd.String)
+ sequence = xsd.Element(Sequence)
+
+
+class Schema(xsd.ComplexType):
+ NAMESPACE = "http://www.w3.org/2001/XMLSchema"
+ targetNamespace = xsd.Attribute(xsd.String)
+ elementFormDefault = xsd.Attribute(xsd.String(enumeration=["qualified", "unqualified"]),
+ use=xsd.Use.OPTIONAL, default = "unqualified")
+ simpleTypes = xsd.ListElement(SimpleType,"simpleType")
+ groups = xsd.ListElement(Group,"group")
+ attributeGroups = xsd.ListElement(AttributeGroup,"attributeGroup")
+ complexTypes = xsd.ListElement(ComplexType,"complexType")
+ elements = xsd.ListElement(Element,"element")
+
+
+SCHEMA = xsd.Schema(
+ targetNamespace = "http://www.w3.org/2001/XMLSchema",
+ elementFormDefault = xsd.ElementFormDefault.QUALIFIED,
+ simpleTypes = [],
+ attributeGroups = [],
+ groups = [],
+ complexTypes = [Enumeration, Pattern, Restriction, List, SimpleType, Element,
+ Sequence, Attribute, AttributeGroup, AttributeGroupReference,
+ Extension, ComplexContent, ComplexType, Group, Schema ],
+ elements = {})
+
+
+
diff --git a/tests/ops.py b/tests/ops.py
new file mode 100644
index 00000000..39a90681
--- /dev/null
+++ b/tests/ops.py
@@ -0,0 +1,100 @@
+import unittest
+from datetime import datetime
+from soapbox import xsd
+
+class Pilot(xsd.String):
+ enumeration = [ "CAPTAIN", "FIRST_OFFICER", ]
+
+class Airport(xsd.ComplexType):
+ INHERITANCE = None
+ INDICATOR = xsd.Sequence
+ code_type = xsd.Element(xsd.String( enumeration =
+ [ "ICAO", "IATA", "FAA",]) )
+ code = xsd.Element(xsd.String)
+
+
+class Weight(xsd.ComplexType):
+ INHERITANCE = None
+ INDICATOR = xsd.Sequence
+ value = xsd.Element(xsd.Integer)
+ unit = xsd.Element(xsd.String( enumeration =
+ [ "kg", "lb",]) )
+
+
+class Ops(xsd.ComplexType):
+ INHERITANCE = None
+ INDICATOR = xsd.Sequence
+ aircraft = xsd.Element(xsd.String)
+ flight_number = xsd.Element(xsd.String)
+ type = xsd.Element(xsd.String( enumeration =
+ [ "COMMERCIAL", "INCOMPLETE", "ENGINE_RUN_UP", "TEST", "TRAINING", "FERRY", "POSITIONING", "LINE_TRAINING",]) )
+ takeoff_airport = xsd.Element(Airport)
+ takeoff_gate_datetime = xsd.Element(xsd.DateTime, minOccurs=0)
+ takeoff_datetime = xsd.Element(xsd.DateTime)
+ takeoff_fuel = xsd.Element(Weight, minOccurs=0)
+ takeoff_gross_weight = xsd.Element(Weight, minOccurs=0)
+ takeoff_pilot = xsd.Element(Pilot, minOccurs=0)
+ landing_airport = xsd.Element(Airport)
+ landing_gate_datetime = xsd.Element(xsd.DateTime, minOccurs=0)
+ landing_datetime = xsd.Element(xsd.DateTime)
+ landing_fuel = xsd.Element(Weight, minOccurs=0)
+ landing_pilot = xsd.Element(Pilot, minOccurs=0)
+ destination_airport = xsd.Element(Airport, minOccurs=0)
+ captain_code = xsd.Element(xsd.String, minOccurs=0)
+ first_officer_code = xsd.Element(xsd.String, minOccurs=0)
+ V2 = xsd.Element(xsd.Integer, minOccurs=0)
+ Vref = xsd.Element(xsd.Integer, minOccurs=0)
+ Vapp = xsd.Element(xsd.Integer, minOccurs=0)
+
+
+class Status(xsd.ComplexType):
+ INHERITANCE = None
+ INDICATOR = xsd.Sequence
+ action = xsd.Element(xsd.String( enumeration =
+ [ "INSERTED", "UPDATED", "EXISTS",]) )
+ id = xsd.Element(xsd.Long)
+
+Schema = xsd.Schema(
+ targetNamespace = "http://flightdataservices.com/ops.xsd",
+ simpleTypes = [ Pilot,],
+ attributeGroups = [],
+ groups = [],
+ complexTypes = [ Airport, Weight, Ops, Status,],
+ elements = { "ops":xsd.Element(Ops), "status":xsd.Element(Status),})
+
+XML_REQUIRED_ONLY = """
+
+ N608WB
+ 123123
+ COMMERCIAL
+
+
+ ICAO
+ EGLL
+
+
+ 2009-12-30T21:35:59
+ 2009-12-30T21:39:59
+
+ ICAO
+ EPWA
+
+
+ 2009-12-30T23:35:59
+ 2009-12-30T23:32:59
+"""
+
+class OPS_Test(unittest.TestCase):
+ def test_required_only(self):
+ ops = Ops.parsexml(XML_REQUIRED_ONLY, Schema)
+ self.assertEqual("N608WB", ops.aircraft)
+ self.assertEqual("123123", ops.flight_number)
+ self.assertEqual("COMMERCIAL", ops.type)
+ self.assertEqual("ICAO", ops.takeoff_airport.code_type)
+ self.assertEqual("EGLL", ops.takeoff_airport.code)
+ self.assertEqual(None, ops.takeoff_pilot)
+ self.assertEqual(datetime(2009,12,30,23,35,59),ops.landing_gate_datetime)
+
+if __name__ == "__main__":
+ unittest.main()
+
\ No newline at end of file
diff --git a/tests/xsd.py b/tests/xsd.py
new file mode 100644
index 00000000..55b40923
--- /dev/null
+++ b/tests/xsd.py
@@ -0,0 +1,548 @@
+import unittest
+from datetime import datetime
+from lxml import etree
+from soapbox import xsd, xsdspec
+
+class Aircraft(xsd.ComplexType):
+ tail_number = xsd.Attribute(xsd.String)
+
+class Airport(xsd.ComplexType):
+ type = xsd.Element(xsd.String)
+ code = xsd.Element(xsd.String)
+
+ @classmethod
+ def create(cls, type, code):
+ airport = Airport()
+ airport.type = type
+ airport.code = code
+ return airport
+
+class Pilot(xsd.String):
+ enumeration = ["CAPTAIN","FIRST_OFFICER"]
+
+class Flight(xsd.ComplexType):
+ tail_number = xsd.Element(xsd.String)
+ takeoff_datetime = xsd.Element(xsd.DateTime, minOccurs = 0)
+ takeoff_airport = xsd.Element(Airport)
+ landing_airport = xsd.Element(Airport)
+ takeoff_pilot = xsd.Element(Pilot, minOccurs = 0)
+ landing_pilot = xsd.Element(Pilot, minOccurs = 0)
+ passangers = xsd.ListElement(xsd.String, "passanger", maxOccurs=10,minOccurs=0)
+
+class ElementTest(unittest.TestCase):
+# This logic have been moved to post rendering validation
+# uncomment when implemented.
+# def test_required(self):
+# tail_number = xsd.Element(xsd.String)
+# try:
+# xmlelement = etree.Element("aircraft")
+# tail_number.render(xmlelement, "tail_number", None)
+# except ValueError:
+# pass
+# else:
+# raise AssertionError("Should get here")
+
+ def test_string_element(self):
+ tail_number = xsd.Element(xsd.String())
+ xmlelement = etree.Element("aircraft")
+ tail_number.render(xmlelement,"tail_number", "LN-KKU")
+ self.assertEqual("""
+ LN-KKU
+
+""",
+ etree.tostring(xmlelement, pretty_print=True))
+
+
+ def test_complex_type_element(self):
+ airport = Airport()
+ airport.type = "IATA"
+ airport.code = "WAW"
+ xmlelement = etree.Element("takeoff_airport")
+ airport.render(xmlelement, airport)
+ expected_xml = """
+ IATA
+ WAW
+
+"""
+ xml = etree.tostring(xmlelement, pretty_print=True)
+ self.assertEqual(expected_xml, xml)
+
+class ListElementTest(unittest.TestCase):
+ def test_rendering_simple_type(self):
+ passangers = xsd.ListElement(xsd.String,"passanger", maxOccurs=10,minOccurs=0)
+ passangers_list = ["abc", "123"]
+ xmlelement = etree.Element("flight")
+ passangers.render(xmlelement, "passanger", passangers_list)
+ expected_xml = """
+ abc
+ 123
+
+"""
+ xml = etree.tostring(xmlelement, pretty_print=True)
+ self.assertEqual(expected_xml, xml)
+
+class BooleanTypeTest(unittest.TestCase):
+ def test_element_true(self):
+ mixed = xsd.Element(xsd.Boolean,)
+ xmlelement = etree.Element("complexType")
+ mixed.render(xmlelement,"mixed", True)
+ expected_xml = """
+ true
+
+"""
+ xml = etree.tostring(xmlelement, pretty_print=True)
+ self.assertEqual(expected_xml, xml)
+
+ def test_attribute_false(self):
+ mixed = xsd.Attribute(xsd.Boolean)
+ xmlelement = etree.Element("complexType")
+ mixed.render(xmlelement,"mixed", True)
+ expected_xml = """\n"""
+ xml = etree.tostring(xmlelement, pretty_print=True)
+ self.assertEqual(expected_xml, xml)
+
+ def test_attribute_nil(self):
+ mixed = xsd.Attribute(xsd.Boolean, nilable = True, use=xsd.Use.OPTIONAL)
+ xmlelement = etree.Element("complexType")
+ mixed.render(xmlelement,"mixed", None)
+ expected_xml = """\n"""
+ xml = etree.tostring(xmlelement, pretty_print=True)
+ self.assertEqual(expected_xml, xml)
+
+class DatetimeTest(unittest.TestCase):
+ def test_rendering(self):
+ dt = datetime(2001, 10, 26, 21, 32, 52)
+ mixed = xsd.Element(xsd.DateTime)
+ xmlelement = etree.Element("flight")
+ mixed.render(xmlelement,"takeoff_datetime", dt)
+ expected_xml = """
+ 2001-10-26T21:32:52
+
+"""
+ xml = etree.tostring(xmlelement, pretty_print=True)
+ self.assertEqual(expected_xml, xml)
+
+
+ def test_wrong_type(self):
+ mixed = xsd.Element(xsd.DateTime,)
+ xmlelement = etree.Element("flight")
+ try:
+ mixed.render(xmlelement,"takeoff_datetime", 1)
+ except Exception:
+ pass
+ else:
+ self.assertTrue(False)
+
+
+
+
+class ComplexTest(unittest.TestCase):
+ def test_rendering(self):
+ airport = Airport()
+ airport.type = "IATA"
+ airport.code = "WAW"
+ xmlelement = etree.Element("airport")
+ airport.render(xmlelement, airport)
+ xml = etree.tostring(xmlelement, pretty_print=True)
+ expected_xml = """
+ IATA
+ WAW
+
+"""
+ self.assertEqual(expected_xml, xml)
+
+ def test_attribute_rendering(self):
+ aircraft = Aircraft()
+ aircraft.tail_number = "LN-KKX"
+ xmlelement = etree.Element("aircraft")
+ aircraft.render(xmlelement, aircraft)
+ expected_xml = """\n"""
+ xml = etree.tostring(xmlelement, pretty_print=True)
+ self.assertEqual(expected_xml, xml)
+
+ def test_attribute_parsing(self):
+ XML = """\n"""
+ aircraft = Aircraft.parsexml(XML)
+ self.assertEqual("LN-KKX", aircraft.tail_number)
+
+
+ def test_mulitylayer_complex(self):
+ flight = Flight()
+ flight.tail_number = "LN-KKA"
+ flight.takeoff_airport = Airport.create("IATA", "WAW")
+ flight.landing_airport = Airport.create("ICAO", "EGLL")
+
+ try:
+ flight.takeoff_pilot = "ABC"
+ except ValueError:
+ pass
+ else:
+ self.assertTrue(False)#should't get here.
+ flight.takeoff_pilot = "CAPTAIN"
+
+ xmlelement = etree.Element("flight")
+ flight.render(xmlelement, flight)
+ xml = etree.tostring(xmlelement, pretty_print=True)
+ expected_xml = """
+ LN-KKA
+
+ IATA
+ WAW
+
+
+ ICAO
+ EGLL
+
+ CAPTAIN
+
+"""
+ self.assertEqual(expected_xml, xml)
+
+
+ def test_complex_with_list(self):
+ flight = Flight()
+ flight.tail_number = "LN-KKA"
+ flight.takeoff_airport = Airport.create("IATA", "WAW")
+ flight.landing_airport = Airport.create("ICAO", "EGLL")
+ flight.passangers.append("abc")
+ flight.passangers.append("123")
+
+ xmlelement = etree.Element("flight")
+ flight.render(xmlelement, flight)
+ xml = etree.tostring(xmlelement, pretty_print=True)
+ expected_xml = """
+ LN-KKA
+
+ IATA
+ WAW
+
+
+ ICAO
+ EGLL
+
+ abc
+ 123
+
+"""
+ self.assertEqual(expected_xml, xml)
+
+
+ def test_inheritance_rendering(self):
+ class A(xsd.ComplexType):
+ name = xsd.Attribute(xsd.String)
+ class B(A):
+ type = xsd.Attribute(xsd.String)
+ b = B()
+ b.name = "b"
+ b.type = "B"
+ xml = b.xml("inheritance")
+ EXPECTED_XML = """\n"""
+ self.assertEqual(EXPECTED_XML, xml)
+
+
+ def test_inheritance_parsin(self):
+ class A(xsd.ComplexType):
+ name = xsd.Attribute(xsd.String)
+ class B(A):
+ type = xsd.Element(xsd.String)
+ XML = """
+ B
+\n"""
+ b = B.parsexml(XML)
+ self.assertEqual(b.name, "b")
+ self.assertEqual(b.type, "B")
+
+
+
+class XmlParsingTest(unittest.TestCase):
+ SIMPLE_XML = """
+
+ EGLL
+ ICAO
+
+ LN-KKA
+ 2001-10-26T21:32:52
+
+ WAW
+ IATA
+
+
+"""
+
+ def test_simple_parsing(self):
+ flight = Flight.parse_xmlelement(etree.fromstring(self.SIMPLE_XML))
+ self.assertEqual("LN-KKA", flight.tail_number)
+ self.assertEqual("WAW", flight.takeoff_airport.code)
+ self.assertEqual("IATA", flight.takeoff_airport.type)
+ self.assertEqual("EGLL", flight.landing_airport.code)
+ self.assertEqual("ICAO", flight.landing_airport.type)
+ self.assertEqual(datetime(2001, 10, 26, 21, 32, 52), flight.takeoff_datetime)
+
+ LIST_XML = """
+
+ EGLL
+ ICAO
+
+ abc
+ 123
+ LN-KKA
+
+ WAW
+ IATA
+
+
+"""
+
+ def test_list_parsing(self):
+ flight = Flight.parse_xmlelement(etree.fromstring(self.LIST_XML))
+ self.assertEqual("LN-KKA", flight.tail_number)
+ self.assertEqual("WAW", flight.takeoff_airport.code)
+ self.assertEqual("IATA", flight.takeoff_airport.type)
+ self.assertEqual("EGLL", flight.landing_airport.code)
+ self.assertEqual("ICAO", flight.landing_airport.type)
+ self.assertEqual(["abc", "123"], flight.passangers)
+
+
+
+class XSD_Spec_Test(unittest.TestCase):
+ AIRPORT_XML = """
+
+
+
+
+
+
+
+
+
+
+
+
+
+ """
+ def test_complexType(self):
+ airport = xsdspec.ComplexType.parse_xmlelement(etree.fromstring(self.AIRPORT_XML))
+ self.assertEqual("airport", airport.name)
+ code_type_element = airport.sequence.elements[0]
+ code_element = airport.sequence.elements[1]
+ self.assertEqual("code_type", code_type_element.name)
+ self.assertEqual("xs:string", code_type_element.simpleType.restriction.base)
+ self.assertEqual(3, len(code_type_element.simpleType.restriction.enumerations))
+ self.assertEqual("ICAO", code_type_element.simpleType.restriction.enumerations[0].value)
+ self.assertEqual("code", code_element.name)
+
+
+SCHEMA_XML = """
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+"""
+
+class SchemaTest(unittest.TestCase):
+ def test_schema_parsing(self):
+ schema = xsdspec.Schema.parse_xmlelement(etree.fromstring(SCHEMA_XML))
+ self.assertEqual(4, len(schema.complexTypes))
+ self.assertEqual(1, len(schema.simpleTypes))
+ self.assertEqual(2, len(schema.elements))
+
+ self.assertEqual("ops", schema.elements[0].name)
+ self.assertEqual("fds:ops", schema.elements[0].type)
+
+ ops_type = schema.complexTypes[2]
+ self.assertEqual("ops", ops_type.name)
+ self.assertEqual("aircraft", ops_type.sequence.elements[0].name)
+ self.assertEqual("xs:string", ops_type.sequence.elements[0].type)
+
+
+
+class RequestResponseOperation(xsd.Group):
+ input = xsd.Element(xsd.String, minOccurs = 0)
+ output = xsd.Element(xsd.String, minOccurs = 0)
+
+class Operation(xsd.ComplexType):
+ name = xsd.Element(xsd.String)
+ requestResponseOperation = xsd.Ref(RequestResponseOperation)
+
+class GroupTest(unittest.TestCase):
+ XML = """
+ TEST-Operation
+ IN
+
+\n"""
+
+ def test_rendering(self):
+ operation = Operation()
+ operation.name = "TEST-Operation"
+ operation.requestResponseOperation.input = "IN"
+ operation.requestResponseOperation.output = "OUT"
+ xml = operation.xml("operation")
+ self.assertEqual(self.XML, xml)
+
+ def test_parsing(self):
+ operation = Operation.parsexml(self.XML)
+ self.assertEqual(operation.name, "TEST-Operation")
+ self.assertEqual(operation.requestResponseOperation.input, "IN")
+ self.assertEqual(operation.requestResponseOperation.output, "OUT")
+
+
+ def test_rendering_empty_group(self):
+ operation = Operation()
+ operation.name = "TEST-Operation"
+ xml = operation.xml("operation")
+ expected_xml = """
+ TEST-Operation
+\n"""
+ self.assertEqual(expected_xml, xml)
+
+
+#
+#
+#
+#
+#
+#
+#
+class TBodyAttributes(xsd.AttributeGroup):
+ encodingStyle = xsd.Attribute(xsd.String, use=xsd.Use.OPTIONAL)
+ use = xsd.Attribute(xsd.String)
+ namespace = xsd.Attribute(xsd.String)
+
+class TBody(xsd.ComplexType):
+ parts = xsd.Attribute(xsd.String)
+ tBodyAttributes = xsd.Ref(TBodyAttributes)
+
+class AttributeGroupTest(unittest.TestCase):
+ def test_rendering(self):
+ body = TBody()
+ body.parts = "Parts"
+ body.tBodyAttributes.use = "required"
+ body.tBodyAttributes.namespace = "xs"
+ expected_xml = """\n"""
+ xml = body.xml("body")
+ self.assertEqual(expected_xml, xml)
+
+ def test_parsing(self):
+ xml = """\n"""
+ body = TBody.parsexml(xml)
+ self.assertEqual(body.parts,"Parts")
+ self.assertEqual(body.tBodyAttributes.use, "required")
+ self.assertEqual(body.tBodyAttributes.namespace, "xs")
+ self.assertEqual(body.tBodyAttributes.encodingStyle, None)
+
+class AirporttDocument(xsd.Document):
+ airport = xsd.Element(Airport)
+
+class DocumentTest(unittest.TestCase):
+ def test_document_rendering(self):
+ document = AirporttDocument()
+ document.airport = Airport(code="XXX", type="IATA")
+ xml = document.render()
+ expected_xml = """
+ IATA
+ XXX
+\n"""
+ self.assertEqual(xml, expected_xml)
+
+ def test_document_parsing(self):
+ XML = """
+ IATA
+ XXX
+ """
+ document = AirporttDocument
+
+if __name__ == "__main__":
+ unittest.main()
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file