This chapter gives an overview of the Python bindings for SIDL. Common aspects of the bindings, such as the mapping of SIDL data types to their Python representatives, are presented in Section 14.2. Issues of concern to callers written in Python are addressed in the client-side bindings discussion in Section 14.3, while issues for callees written in Python are in Section 14.4, which describes the implementation-side bindings.
NOTE: Babel requires a Python shared library. Because Python 2.3 has a configure/build system that builds shared libraries on many architectures, use of Python 2.3 or beyond is recommended.
As with any programming language-neutral technology, translations must be made between abstract constructs supported by the technology and the corresponding concrete constructs in the native programming language. Due to the need to identify types in a global context, Subsection 14.2.1 describes the convention used to establish name spaces. Conventions for generating language-specific method signatures are given in Subsection 14.2.2. The mapping of SIDL fundamental types is given in Subsection 14.2.3. Finally, the process of casting between different types is described in Subsection 14.2.4.
As in the case of Java, the SIDL name space maps easily into Python packages and modules. That is, the SIDL package and interface/class hierarchy maps to a corresponding Python hierarchy. For example, a class Z in a SIDL specification with the top-most package X and nested, parent package Y maps to a Python package X with module Y that contains method Z. The class is then identified as X.Y.Z in Python.
Despite the natural name space mapping, the goal to make language bindings as natural as possible led to some variation for Python method signatures. While the method name is used directly — where the full name (i. e., the short name with the extension appended) is used for overloaded methods — there are some differences for its arguments. Specifically, arguments to the method include only the specified in and inout arguments, while the return value of the Python method includes the SIDL return value in addition to the inout and out parameters. (This will, hopefully, seem natural to Python programmers.) For example, the following SIDL declaration for method passeverywhere of class Cdouble within the Args package is:
double passeverywhere( in double d1, out double d2, inout double d3 ); |
The corresponding calling signature — based on Python’s built-in documentation capability — is:
passeverywhere(in double d1, inout double d3) RETURNS (double _return, out double d2, inout double d3) |
Whenever the SIDL specification includes a return type, the corresponding Python signature will include an _return as the first value of the Python’s RETURNS value. Starting _return with an underbar is used to indicate the argument is not a parameter since Python parameter names cannot begin with an underbar. More information on Python’s built-in documentation capability is given in Subsection 14.3.4.
NOTE: As discussed in Subsection 14.2.3, methods passing raw SIDL arrays (or r-arrays) do not have index arguments in Python.
Unlike the other bindings, a straightforward mapping of SIDL to Python types does not exist. Consequently, this subsection describes key data types.
As stated in Section 6.3, SIDL longs are equivalent to those in C. Hence, they map to 64-bit integers in the middleware. However, since Python’s unlimited precision integer data type is used in the bindings, the behaviour is not exactly like 64-bit integers (i. e., there is no overflow).
NOTE: For Python versions before 2.2, the code needs to guarantee that a Python unlimited precision integer is used whenever a SIDL long is needed. For example, calling isPrime — whose SIDL signature is bool isPrime(long num) — as isPrime(1) will fail. However, calling isPrime(1L) will succeed.
As stated in Section 6.3, SIDL ints for Python are equivalent to those in C. Hence, they map to a 32-bit integer.
Python exceptions must be Python classes; they cannot be C extension types — the mechanism used to wrap SIDL objects as Python objects. Because of this, Babel defines an exception class for each SIDL type that implements sidl.BaseException. For a type called X.Y.Z, the Python exception class is named X.Y.Z._Exception.
NOTE: In Babel 0.10.2 and previous releases, the Python exception class was named X.Y.Z.Exception, but this name can potentially collide with the class constructor or a static method named Exception. For backwards compatibility, Babel defines X.Y.Z.Exception if the name Exception is not used in the class.
As discussed in Section 6.4, SIDL supports both normal and raw arrays (i. e., r-arrays). Both types of arrays are treated the same in Python bindings. That is, they both map to their NumPy or Numeric Python equivalents. In the case of SIDL longs, an array of 64-bit integers may be used if NumPy or Numeric Python supports 64-bit integers; otherwise, an array of Python’s indefinite precision integers (i. e., integers with unlimited bits) are used.
NOTE: The SIDL array API is not supported in these bindings; instead, those in NumPy or Numeric Python must be used. With Babel 1.1.0 and later, Babel supports either the new NumPy or the deprecated Numeric Python. To determine which Babel is configured to use, you can use the following:
import sidlPyArray if sidlPyArray.type == "numpy": import numpy else: if sidlPyArray.type == "numeric": import numeric |
There is no way to access the value of a SIDL opaque in Python. The Python value None can be passed as an incoming opaque parameter. You can also pass in an opaque value that was returned by previous Babel method invocation. Babel maps SIDL opaque to the Python CObject type — a type that is only accessible from Python C extension modules.
Given an object, obj, it is possible to determine if it is an instance of a SIDL class or interface whose fully qualified name is X.Y.Z as follows:
>>> import X.Y.Z
>>> zobj = X.Y.Z.Z(obj)
Of course, the import is not needed if X.Y.Z has already been imported. If zobj is not equal to None, the cast was successful.
This section summarizes aspects of generating and using the Python bindings associated with software wrapped with Babel’s language interoperability middleware. The bindings generation process and required environment variables are presented before the process for importing SIDL-specified constructs is described. Object management and invocation of static and overloaded methods are also summarized. The process of catching exceptions is then discussed. Finally, the processes for enabling and disabling implementation-specific pre- and post-method instrumentation — referred to as “hooks” — are illustrated.
Building Python bindings requires an installation with Python compiled as a shared or dynamically linked library. The standard Python build only creates the necessary shared library on a few platforms — none of which are target platforms for Babel. Some Linux distributions include a Python shared library, and it is possible to make a Python shared library on Solaris. The Python shared library should contain the objects from libpythonm.n.a where m.n is the version of Python being used. Since making a shared library is different on each platform, the process is not covered here.
To generate client-side bindings, Babel must be run as follows1:
% babel --exclude-external --client=python file.sidl
or simply
% babel -E -c=python file.sidl
This creates the Intermediate Object Representation (IOR) files in the current directory and a tree of subdirectories based on the package hierarchy found in file.sidl. It also creates module (i. e., _Module.c) files in the appropriate subdirectories. In most cases, the IOR, _pSkel.c, and _pLaunch.c files must be compiled and place in a shared library.
There are three environment variables associated with running Python with Babel on many systems. Each, described below, identifies path-related directories that must be set properly for various tools. It is assumed Babel was already installed in directories rooted at $PREFIX.
The following command is used to import a class named X.Y.Z:
>>> import X.Y.Z
Alternatively,
>>> from X.Y.Z import *
can be used but there can be no name space collisions.
This subsection elaborates on a few of the common problems (and possible solutions) that occur when importing SIDL-specified extensions.
>>> import X.Y.Zmodule
Traceback (innermost last):
File "<stdin>", line 1, in ?
ImportError: dynamic module does not define init function (initZmodule)
This could be a matter of an incorrect import or a problem with the environment. Consider the following:
If the answers to these questions do not solve the problem, submit a bug report for Babel.
>>> import X.Y.Z
Fatal Python error: Cannot load implementation for SIDL class X.Y.Z
Abort (core dumped)
then the Python stub code — the code that links Python to SIDL’s independent object representation (IOR) — failed in its attempt to load the shared or dynamically linked library that contains the implementation of SIDL class X.Y.Z. This is likely to be a path problem so consider the following:
>>> import X.Y.Z
Fatal Python error: Cannot load implementation for SIDL interface X.Y.Z
Abort (core dumped)
it is the same problem described for the fatal error encountered when attempting to load a SIDL class.
Once the Python extension module is built and imported, an instance can be created. For example, given the Args.Cdouble example in Subsection 14.2.2 with the method passeverywhere, the process for instantiating the class and printing its calling signature is:
$ python >>> import Args.Cdouble >>> obj = Args.Cdouble.Cdouble() >>> print obj.passeverywhere.__doc__ passeverywhere(in double d1, inout double d3) RETURNS (double _return, out double d2, inout double d3) |
In this case the last part of the class name is repeated when assigning the instance to obj. Any SIDL document comments (i. e. comments enclosed in /** */) will appear below the signature documentation.
In some cases, the Python extension module may be named Cdoublemodule.so instead of simply Cdouble.so. This might result in the temptation to import Args.Cdoublemodule instead of import Args.Cdouble; resist!
Static methods of a SIDL-specified class are available in Python. Since they are associated with a class, they reside in its name space.
Examples of calls to SIDL overloaded methods are based on the overload_sample.sidl file shown in Section 6.7. Recall that the file describes three versions of the getValue method. The first takes no arguments, the second takes an integer argument, and the third takes a boolean. Each is called in the code snippet below:
b1 = 1 i1 = 1 t = Overload.Sample.Sample() nresult = t.getValue() iresult = t.getValueInt(i1) bresult = t.getValueBool(b1) |
SIDL exceptions are caught very much like normal Python exceptions except the Python exception class of the SIDL type must be used. The exception value holds the SIDL object as attribute exception. Below is an example of catching exceptions from a call to getFib.
try: fib.getFib(-1, 10, 10, 0) except ExceptionTest.NegativeValueException._Exception: (etype, eobj, etb) = sys.exc_info() # eobj is the SIDL exception object print eobj.exception.getNote() # show the exception comment print eobj.exception.getTrace() # and traceback |
Note that eobj.exception is an instance of ExceptionTest.NegativeValueException.NegativeValueException, the Python type corresponding to the SIDL type ExceptionTest.NegativeValueException.
If a given component supports pre- and post-method invocation instrumentation, also known as “hooks”, their execution can be enabled or disabled at runtime through the built-in _set_hooks method. For example, given the following SIDL specification:
package hooks version 1.0 { class Basics { /** * Basic illustration of hooks for static methods. */ static int aStaticMeth(in int i, out int o, inout int io); /** * Basic illustration of hooks for static methods. */ int aNonStaticMeth(in int i, out int o, inout int io); } } |
which has a single static function and a member function for the Basics class, the processes for enabling and disabling execution of the implementation-specific hooks are:
obj = hooks.Basics.Basics() # # Enable hooks execution (enabled by default) # ...for non-static methods # hooks.Basics._set_hooks_static(1) # # ...for static methods # obj._set_hooks(1) # # ...do something important... # # # Disable hooks execution # ...for non-static methods # hooks.Basics._set_hooks_static(0) # # ...for static methods # obj._set_hooks(0) # # ...do something important... # |
It is important to keep in mind that the _set_hooks_static method must be used to enable/disable invocation of hooks for static methods and the _set_hooks method must be used for those of non-static methods. Also, Babel does not provide client access to the _pre and _post methods; therefore, they cannot be invoked directly. More information on the instrumentation process is provided in Subsection 8.4.5.
Interface contracts specify the expected behaviors of clients and servers of interface and class methods. Once specified, contracts can automatically be enforced at runtime. This section provides an example of a specification and associated code snippets for performing basic, traditional contract enforcement — introduced in Section 6.5 — within a Python client.
A SIDL specification, including preconditions and postconditions, for calculating the sum of two vectors is given below. (Refer to Section 6.5 for an introduction to the contract syntax.) According to the preconditions, all callers are expected to provide two one-dimensional, SIDL arrays of the same size as arguments. The postconditions specify that all implementations are expected to return a non-null, one-dimensional array of the same size (as the first SIDL array), assuming the preconditions are satisfied.
package vect version 1.0 { class Utils { /* ... */ /** * Return the sum of the specified vectors. */ static array<double> vuSum(in array<double> u, in array<double> v) throws sidl.PreViolation, sidl.PostViolation; require not_null_u: u != null; u_is_1d : dimen(u) == 1; not_null_v: v != null; v_is_1d : dimen(v) == 1; same_size: size(u) == size(v); ensure no_side_effects : is pure; result_not_null: result != null; result_is_1d : dimen(result) == 1; result_correct_size: size(result) == size(u); } /* ... */ } |
An example of a Python client calling the method is given below. The code snippet illustrates declaring and creating the arrays; enabling full contract enforcement (i. e., checking all contract clauses); executing vuSum; and handling contract violation exceptions is given below.
import sidl.EnfPolicy import sidl.PostViolation import sidl.PreViolation import vect.Utils import sidl.ContractClass ALL_TYPES = sidl.ContractClass.ALLCLASSES PRECONDITIONS = sidl.ContractClass.PRECONDS POSTCONDITIONS = sidl.ContractClass.POSTCONDS import sidlPyArrays if sidlPyArrays.type == "numpy": from numpy import zeros, float64, ndarray ArrayType = ndarray elif sidlPyArrays.type == "numeric": import Numeric zeros = Numeric.zeros float64 = Numeric.Float64 ArrayType = Numeric.ArrayType def savespace(o): try: o.savespace(1) except AttributeError: pass def createDouble(len): result = None if (len >= 0): result = zeros((len, ), float64) savespace(result) return result # ... u = createDouble(MAX_SIZE) v = createDouble(MAX_SIZE) # Initialize u and v. # # Enable FULL contract enforcement. try: sidl.EnfPolicy.setEnforceAll(ALL_TYPES, TRUE) except: # Handle the exception # Do something meaningful before execute method. try: x = vect.Utils.vuSum(u, v) if (x != None): # Do something with the result, x. except: (excType, excObj, ExcTb) = sys.exc_info() if (excObj): try: if (excObj.exception.isType("sidl.PreViolation")): # Handle precondition violation elif (excObj.exception.isType("sidl.PostViolation")): # Handle postcondition violation else: # Handle unexpected exception except: # Handle exception # ... |
Alternative enforcement options can be set, as described in Section 6.5, through the two basic helper methods: setEnforceAll and setEnforceNone. The code snippet below shows the Python calls associated with the traditional options of enabling only precondition enforcement, enabling postcondition enforcement, or completely disabling contract enforcement.
import sidl.EnfPolicy import sidl.ContractClass # # Enable only precondition contract enforcement. # (Useful when only need to ensure callers comply with contract.) try: sidl.EnfPolicy.setEnforceAll(sidl.ContractClass.PRECONDS, FALSE) except: # Handle the exception # # Enable only postcondition contract enforcement. # (Useful when only need to ensure implementation(s) comply with contract.) try: sidl.EnfPolicy.setEnforceAll(sidl.ContractClass.POSTCONDS, FALSE) except: # Handle the exception # # Disable contract enforcement. # (Should only be used when have confidence in caller AND implementation.) try: sidl.EnfPolicy.setEnforceNone(FALSE) except: # Handle the exception |
This section illustrates the basic interfaces and processes for traditional interface contract enforcement for a Python client. Additional enforcement policy options and methods as well as more information regarding the specification and enforcement of contracts can be found in Chapter 21.
This section summarizes aspects of generating and wrapping software written in Python. The bindings generation process is presented first. The process for defining and managing that data is then discussed. The process of throwing exceptions in the implementation is then illustrated. Finally, the results of generating implementations with pre- and post-method “hooks” are shown.
As mentioned in Subsection 14.3.1, Python must have been compiled as a shared or dynamically linked library. To implement an object in Python, Babel must first create the Python implementation-side bindings2 as follows:
% babel --exclude-external --server=python file.sidl
or simply
% babel -E -s=python file.sidl
This creates the IOR, Python skeletons (i. e., _pSkel.c), and Python launch (i. e., _pLaunch.c) files in the current directory. In most cases, the IOR, _pSkel.c, and _pLaunch.c files must be compiled and place in a shared library. It also creates a tree of subdirectories based on the package hierarchy found in file.sidl in which it generates Python extension modules for the client-side binding (i. e., _Module.c) and implementation (i. e., _Impl.py) files. The implementation files need to be filled in, as described in Subsection 14.4.2, and extension modules compiled as discussed in Subsection 14.4.3.
Implementation details must be added to the “Impl” files generated in Subsection 14.4.1. Changes to these files must be made between code splicer pairs to ensure their retention in subsequent invocations of Babel. In fact, Babel generates everything except the code that appears between splicer blocks (i. e., splicer.begin and splicer.end comments). That is, it creates a class definition and empty methods in files whose names end in _Impl.py. Code placed within matching splicer pairs will be preserved in subsequent executions of Babel while changes outside them will be lost.
Using the example from Subsection 14.2.2, the splicer blocks and implementation details for passeverywhere are:
def passeverywhere(self, d1, d3): # # SIDL EXPECTED INCOMING TYPES # ============================ # double d1 # double d3 # # # SIDL EXPECTED RETURN VALUE(s) # ============================= # (_return, d2, d3) # double _return # double d2 # double d3 # # DO-NOT-DELETE splicer.begin(passeverywhere) if (d1 == 3.14): retval = 3.14 else: retval = 0 return (retval, 3.14, -d3) # DO-NOT-DELETE splicer.end(passeverywhere) |
Babel creates a setup.py file that can be used to build the Python extension modules that you create. setup.py uses the Python distutils package to build the Python extension modules. The following are two extra command line arguments:
The directory containing the SIDL runtime and Python headers is normally specified with --include-dirs=. The directory where libsidl.so is stored must also be specified. The following is a hypothetical example:
setup.py --include-dirs=/usr/local/include
--include-dirs=/usr/local/include/python
--library-dirs=/usr/local/lib build_ext --inplace
although any real installation is unlikely to actually use those settings.
Any variables declared in the implementation source file will, by virtue of Babel’s encapsulation, be private.
NOTE: Python does not support the built-in, class-wide _load() method used for one-time initialization in the other language bindings.
Recall Subsection 14.2.3 discussed issues associated with support for SIDL exceptions. Below is an example snippet of code for throwing the exceptions that are caught in the Subsection 14.3.7 example. The setNote method provides a useful error message, and the add method helps provide a multi-language traceback capability — provided each layer of the call stack calls add.
def getFib(self, n, max_depth, max_value, depth): # sidl EXPECTED INCOMING TYPES # ============================ # int n, max_depth, max_value, depth # # sidl EXPECTED RETURN VALUE(s) # ============================= # int _return # DO-NOT-DELETE splicer.begin(getFib) if (n < 0): ex = ExceptionTest.NegativeValueException.NegativeValueException() ex.setNote("n negative") ex.add(__name__, 0, "ExceptionTest.Fib.getFib") raise ExceptionTest.NegativeValueException._Exception, ex # numerous lines deleted # DO-NOT-DELETE splicer.end(getFib) |
As discussed in Subsection 14.3.8, when hooks execution
is enabled, implementation-specific instrumentation is executed. Using
the --
generate-hooks option on the Babel
command line when generating implementation-side bindings results
in the automatic generation of a _pre and _post
method for every static and non-static method associated with each class
in the specification. For the aStaticMethod specified in
Subsection 14.3.8, the generated _pre method
implementation is:
def aStaticMeth_pre(i, io): # # sidl EXPECTED INCOMING TYPES # ============================ # int i # int io # # # sidl EXPECTED RETURN VALUE(s) # ============================= # # None # """\ Basic illustration of hooks for static methods. """ # DO-NOT-DELETE splicer.begin(aStaticMeth_pre) # # Add instrumentation here to be executed immediately prior # to dispatch to aStaticMeth(). # return # DO-NOT-DELETE splicer.end(aStaticMeth_pre) |
while that of the _post method is:
def aStaticMeth_post(i, o, io, _retval): # # sidl EXPECTED INCOMING TYPES # ============================ # int i # int o # int io # int _retval # # # sidl EXPECTED RETURN VALUE(s) # ============================= # # None # """\ Basic illustration of hooks for static methods. """ # DO-NOT-DELETE splicer.begin(aStaticMeth_post) # # Add instrumentation here to be executed immediately after # return from dispatch to aStaticMeth(). # return # DO-NOT-DELETE splicer.end(aStaticMeth_post) |
Per the normal implementation process, the desired instrumentation should be added within the splicer blocks of aStaticMethod_pre and aStaticMethod_post. As stated in the comments within those blocks, aStaticMethod_pre will be executed immediately prior to dispatch to aStaticMethod when the latter is invoked by a client. Assuming no exceptions are encountered, aStaticMethod_post is executed immediately upon return from aStaticMethod.