Binding Model¶
The binding model represents the relations between Python classes that correspond to schema components. The class hierarchy for the binding model is depicted in the following diagram.
There are three primary groups of classes, which in turn depend on some supporting capabilities, all of which are described in the following sections.
Supporting Capabilities¶
Common Binding Instance Features¶
pyxb.binding.basis._TypeBinding_mixin
is a marker class to indicate
that the incorporating class represents a binding associated with a type
definition (whether simple or complex). The key features of this mixin are:
- The
_ExpandedName
class variable is overridden in each class to identify the type definition corresponding to the class.- The
_XSDLocation
class variable is overridden in each class to provide the line, column, and schema at which the data type definition was found. This is used in diagnostics.- The
namespace context
of the type definition is recorded to allow users to perform QName resolution of values within instance documents (see, for example, the customized bindings inpyxb.bundles.wssplat.wsdl11
).- Each instance records the
pyxb.binding.basis.element
instance that determines where the type came from. The element is required in order to provide the correct name when converting the binding instance to a DOM instance on its way to expression as a text XML document.- The mixin is also where xsi:nil information for the instance is stored.
- A
Factory
infrastructure is provided to allow creation of new instances of the binding while permitting developers to customize the generated binding classes; see Support for Customization.
Deconflicting Names¶
In XML schema, the namespaces for element declarations, type definitions, and
attribute definitions are all distinct. Python uses the same namespace for
everything. So, if you have a schema that defines a simple type color
,
the schema can also have an element named color
with a complex type that
itself has both a child element named color
and a distinct attribute (of
type color
) that is also named color
. Since the natural
representation of elements and attributes inside complex types is also by
their XML name, the chances of conflict are high.
PyXB resolves this by ensuring every identifiable object has a unique identifier within its context. The steps involved are:
- Make object name into an
identifier
by stripping out non-printable characters, replacing characters that cannot appear in identifiers with underscores, stripping leading underscores, and prefixing an initial digit with the charactern
. Deconflict
the resulting identifier from Pythonreserved identifiers
and other context-specific keywords.- Prepend the standard prefix that denotes the identifier’s visibility (public, protected, private)
- Make the resulting identifier
unique
within its context (containing class or module).
These steps are encapsulated into a single function
pyxb.utils.utility.PrepareIdentifier
which takes parameters that
customize the context for the identifier.
In addition to name conflicts with namespace-global identifiers appearing
directly in the module, conflicts may also appear within a binding class as a
result of collision with names from Python keywords, public class names, and
public field or method names in the class. The
pyxb.utils.utility._DeconflictSymbols_mixin
is used to refine the set
of type-specific public names. If you customize a generated binding class by
extending from it, you must specify your own class variable
_ReservedSymbols
with a value that is the union of your symbols and those
of the superclass(es) (see pyxb.utils.utility._DeconflictSymbols_mixin
for details).
Deconfliction of module-level names occurs prior to code generation
. Identifiers are deconflicted in
favor of higher items on this list:
- Python keywords
- Public class identifiers
- Element tags
- Complex or simple type definition tags
- Enumeration tags
- Attribute tags
Support for Customization¶
One of the primary goals of PyXB is to support Python modules which customize
the generated bindings by adding both functionality and derived content.
Maintenance issues require that these extensions exist separately from the
automatically-generated binding modules; usability requires that they inherit
from the automatically-generated modules. This is supported by the
pyxb.binding.basis._DynamicCreate_mixin
class.
This class provides a method
which is used by
the generated bindings to create new instances of themselves. The raw
bindings are generated into a sub-module with the prefix raw
, and the
extensions modify the generated class to record the real class that should
be used when new instances are created as a result of converting an XML
document into a binding object.
For example, if a binding is to be created in a module dinner
, the
--generate-raw-binding
flag would be used on pyxbgen to
generate the binding in a file named raw/dinner.py
. The wrapper module
dinner.py
would contain the following code (assuming that the class
parsnip
was to be extended):
# Bring all public symbols up from the generated one
from raw.dinner import *
# Bring them in again, but left with their original module path
import raw.dinner
# Replace the generated parsnip with a customizing extension
class parsnip (raw.dinner.parsnip):
# Customization here
pass
# Register the customization for use by the binding infrastructure
raw.dinner.parsnip._SetSupersedingClass(parsnip)
With this pattern, objects created by the user through dinner.parsnip()
and from XML documents by the CreateFromDOM
infrastructure will both be
instances of the extending wrapper class.
Simple Type Definitions¶
Simple type definitions derive from
pyxb.binding.basis.simpleTypeDefinition
and a standard Python type.
For simple types that are not derived by list or union, you can construct
instances using the Factory
method or directly, providing the value as an argument. New instance creation
is validated against the facets recorded in the binding class.
Constraining Facets¶
Each class corresponding to a simple type definition has class variables for
the constraining facets
that are valid for that class. These variables are named by prefixing the
facet name with _CF_
, and have a value that is an instance of the
corresponding facet class
. Where possible, the
variables are inherited from the parent class; when a simple type is derived
by restriction, the restricted class overrides its parent with a new value
for the corresponding facet.
Facets incorporate schema-specific constraining values with some code that validates potential instances of the type against the constraints. Constraining values may:
- be of a fixed type, as with length;
- take on a value in the value space of the simple type in which the facet appears, as with minInclusive; or
- take on a value in the value space of the superclass of the simple type in which the facet appears, as with minExclusive;
Enumeration and pattern constraints maintain a list of the respective acceptable enumeration and pattern values.
Facets implement the
pyxb.binding.facets.ConstrainingFacet.validateConstraint
method, which
in turn is invoked by the
pyxb.binding.basis.simpleTypeDefinition.XsdConstraintsOK
class method
when given a value that may or may not satisfy the constraints. The
Factory
will normally
validate the constraints before allowing a new instance to be returned.
List Types¶
Simple types that derive by list extend from
pyxb.binding.basis.STD_list
which in turn descends from the Python
list
type. These derived classes must override the base class
pyxb.binding.basis.STD_list._ItemType
value with the appropriate
class to use when creating or validating list members.
When constructing an instance of a simple list type, you can provide a list as the initializer. The members of the list must be valid initializers to the underlying item type.
Union Types¶
Union types are classes that are never instantiated. Instead, the binding
classes define a pyxb.binding.basis.STD_union._MemberTypes
variable
which contains a list of binding classes that are permitted as members of the
union. The pyxb.binding.basis.STD_union.Factory
method attempts, in
turn, to create an instance of each potential member type using the arguments
passed into it. The returned value is the first instance that was
successfully created.
Note that this means the fact that a particular attribute in an element is a member of a union is not recorded in the attribute value. See Attribute Uses.
It is not possible to construct an instance of a union type directly. You
must use the Factory
method,
with an argument that is acceptable as an initializer for one of the member
types.
Complex Type Definitions¶
Complex type definitions derive from
pyxb.binding.basis.complexTypeDefinition
. Classes representing complex
type definitions record maps that specify attribute
and element
use structures to record the attributes and
elements that comprise the type. Each such structure is stored as a private
class field and is used by Python properties to provide access to element and
attribute values in a binding instance.
The base pyxb.binding.basis.complexTypeDefinition
class provides the
infrastructure to identify the appropriate attribute or element given an XML
tag name. For classes corresponding to types that enable wildcards, it also provides a mechanism
to access unrecognized elements and attributes. Wildcard elements
are converted to
binding instances if their namespace and name are recognized, and otherwise
are left as DOM nodes. Wildcard attributes
are stored in
a map from their expanded name
to the
unicode value of the attribute.
When creating a complex type definition, you can provide the values for its attributes and fields through arguments and keywords. Keywords whose name matches an attribute or element identifier are used to set that element, bypassing the content model. Arguments are processed in order using the content model for identification and validation. See the example below.
Elements¶
Each element corresponds to a field in the binding instance; the field is managed through a element declaration structure. Element names are disambiguated, and a Python property is defined to retrieve and set the element value.
When the content model permits multiple occurrences of the element, its value is a Python list. The order in this list is significant.
Attributes¶
Each attribute corresponds to a field in the binding instance; the field is managed through a attribute use structure. Attribute names are disambiguated, and a Python property is defined to retrieve and set each attribute value.
Note that if the same name is used for both an attribute and an element, the element use takes priority. See Deconflicting Names.
Simple Content¶
Complex types with simple content (i.e., those in which the body of the
element is an octet sequence in the lexical space of a specified simple type)
are distinguished by providing a value for the class-level
pyxb.binding.basis.complexTypeDefinition._TypeDefinition
variable.
For these types, the pyxb.binding.basis.complexTypeDefinition.content
method returns the instance of that type that corresponds to the content of
the object.
Users of bindings must be aware of whether a particular value is a true simple
type, or a complex type with simple content. In the former case, the value
descends from the corresponding Python type and can be used directly in Python
expressions. In the latter, the value must be retrieved using the value
method before it can be used.
As an example, consider this schema (available in examples/content
):
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="numbers">
<xs:complexType>
<xs:sequence>
<xs:element name="simple" type="xs:integer"/>
<xs:element name="complex">
<xs:complexType>
<xs:simpleContent>
<xs:extension base="xs:integer">
<xs:attribute name="style" type="xs:string"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
</xs:element>
</xs:sequence>
<xs:attribute name="attribute" type="xs:integer"/>
</xs:complexType>
</xs:element>
</xs:schema>
With the generated bindings, the following program:
from __future__ import print_function
from pyxb import BIND
import content
v = content.numbers(1, BIND(2), attribute=3)
v.complex.style = "decimal"
print(v.toxml("utf-8").decode('utf-8'))
print(3 * v.simple)
print(4 * v.complex.value())
print(5 * v.attribute)
produces the following output:
<?xml version="1.0" encoding="utf-8"?><numbers attribute="3"><simple>1</simple><complex style="decimal">2</complex></numbers>
3
8
15
Note that it was necessary to indicate that the second member (complex
) of
the numbers
element needs to be wrapped in an instance of the appropriate
complex type. Similarly, it was necessary to add the call to value()
on
the value of v.complex
in order to get a valid Python numeric value. This
was not necessary for v.simple
or v.attribute
.
Mixed and Element-Only Content¶
Mixed and element-only content nodes
use the pyxb.binding.basis.complexTypeDefinition._ElementBindingDeclForName
method
to manage the mapping from XML schema element names to class members. The
element and attribute names are distinct. Instances of complex types also
reference a content automaton
to ensure the constraints of
the schema are satisfied. These structures are described in
Content Model.
For these types, the pyxb.binding.basis.complexTypeDefinition.content
method returns a list, in parsed order, of the Python objects and (if mixed)
non-element content that belong to the instance. Be aware that this order
currently does not influence the order of elements when converting bindings
into XML.
Elements¶
Unlike the bindings for schema type definitions, which are represented as
Python classes, bindings corresponding to XML Schema element declarations are
represented as instances of the pyxb.binding.basis.element
class. The
instances can be used to create new binding instances that are associated with
the element. Elements are used in the content model to identify transitions
through a finite automaton.
You can use elements just like types in that they are invokable, with arguments corresponding to the arguments of the constructor of the corresponding type. See the example above.