Ctypes API generation¶
One of the parts of the Wrappyr project is the Ctypes API builder. It loads a description of a Ctypes based API (typically from XML) and generates Python code from that. The code that does this lives in wrappyr.ctypes_api_builder. Before Wrappyr generates the Python code, it gives you the chance to manipulate the API by calling the the process_ctypes_structure of your Package class. Here you can do things like:
- Grouping C functions into classes
- Renaming classes, methods, parameter names to match PEP8 naming conventions
- Organizing the API into packages
- And more...
This document describes all classes used to represent a Python API that you can manipulate and examples on how to use this API.
Reference¶
-
class
wrappyr.ctypes_api_builder.structure.Node¶ The base class for all classes used to represent the API.
-
name¶ The name of this node, which will be used to look up Nodes by path.
-
parent¶ The parent node of this node. It will be set automatically if you you make this node a child of another node, like when you add a
Methodto aClass
-
get_path([top])¶ Get the dotted path from top to this node. If top is not given, the path from the furthest ancestor is returned.
>>> struct = CTypesStructure() >>> Box2D = Package("Box2D") >>> dyn = Package("dynamics") >>> Box2D.add_package(dyn) >>> dyn.get_path() 'dynamics' >>> struct.add_package(Box2D) >>> dyn.get_path() 'Box2D.dynamics' >>> dyn.get_path(Box2D) 'dynamics'
-
get_closest_parent_of_type(type)¶ Get the closest ancestor of type
>>> p = Package("package") >>> m = Module("module") >>> c = Class("class") >>> p.add_module(m) >>> m.add_class(c) >>> m == c.get_closest_parent_of_type(Module) True >>> p == c.get_closest_parent_of_type(Package) True
-
get_closest_parent_module()¶ Shorthand for get_closest_parent_of_type(Module)
-
get_distance_to_parent(parent)¶ Returns the depth of this node calculated from parent or None if parent is not an ancestor of this node.
>>> p = Package("package") >>> m = Module("module") >>> c = Class("class") >>> p.add_module(m) >>> m.add_class(c) >>> c.get_distance_to_parent(m) 1 >>> c.get_distance_to_parent(p) 2
-
find_lowest_common_parent(other)¶ Returns the first ancestor that this node shares with other
>>> dynamics = Package("dynamics") >>> b2Body = Class("b2Body") >>> joints = Package("joints") >>> b2WeldJoint = Class("b2WeldJoint") >>> dynamics.add_class(b2Body) >>> dynamics.add_package(joints) >>> joints.add_class(b2WeldJoint) >>> dynamics == b2WeldJoint.find_lowest_common_parent(b2Body) True
-
parents()¶ Returns a generator that iterates over all ancestors starting with the closest one.
>>> dynamics = Package("dynamics") >>> joints = Package("joints") >>> b2WeldJoint = Class("b2WeldJoint") >>> dynamics.add_package(joints) >>> joints.add_class(b2WeldJoint) >>> tuple(b2WeldJoint.parents()) == (joints, dynamics) True
-
-
class
wrappyr.ctypes_api_builder.structure.Module(Node)¶ Class used to represent a Python module. A module can contain:
You can create a
Modulefrom XML by using the <module> tag:<module name="test"> ... </module>
-
find_library([name])¶ Find
Librarywith specified name in thisModuleor one of it’s parents. If name is not given, it will search for a library specified as default.
-
add_library(library)¶ Add
Libraryinstance library to this module and set its parent to this node. This does not remove it from its current parent, so you must remove it from its parent first (if it has one of course).
-
remove_library(library)¶ Remove
Libraryinstance library from this module and set its parent toNone.
-
add_class(class)¶ Add
Classinstance class to this module and set its parent to this node. This does not remove it from its current parent, so you must remove it from its parent first (if it has one of course).
-
add_function(function)¶ Add
Functioninstance function to this module and set its parent to this node. This does not remove it from its current parent, so you must remove it from its parent first (if it has one of course).
-
remove_function(function)¶ Remove
Functioninstance function from this module and set its parent toNone.
-
add_pointer(pointer)¶ Add
PointerTypeinstance pointer to this module and set its parent to this node. This does not remove it from its current parent, so you must remove it from its parent first (if it has one of course).Since a
PointerTypedoes not need to be a child of a Node to be used, this is probably only useful for loading from XML.
-
remove_pointer(pointer)¶ Remove
PointerTypeinstance pointer from this module and set its parent toNone.
-
every_pointer()¶ Returns all
PointerTypeinstances that this module contains.
-
exception
LibraryNotFound(Exception)¶ Exception thrown by
Module.find_library()when it cannot find the requestedLibrary.
-
-
class
wrappyr.ctypes_api_builder.structure.Package(Module)¶ Class used to represent a Python package. In addition to everything a
Modulecan contain, a package can contain:You can create a
Packagefrom XML by using the <package> tag:<package name="test"> ... </module>
-
add_package(package)¶ Add
Packageinstance package to this package and set its parent to this node. This does not remove it from its current parent, so you must remove it from its parent first (if it has one of course).
-
remove_package(package)¶ Remove
Packageinstance package from this package and set its parent toNone.
-
-
class
wrappyr.ctypes_api_builder.structure.Library(Node)¶ Class used to represent a C library. A library will be available for the
Nodeit’s placed in (either aPackageor aModule) and all of its descendents.You can create a
Libraryfrom XML by using the <library> tag:<package name="test"> <library name="libA" path="libA.so" default="true" /> <library name="libB" path="libB.so" /> <function name="func_a"> <call symbol="funcA"> <!-- Since libA is the default for this Package, the symbol funcA will be retrieved from libA.so --> ... </call> </function> <function name="func_b"> <call symbol="funcB" library="libB"> <!-- We've explicitly chosen libB, so the symbol funcB will be retrieved from libB.so --> ... </call> </function> </module>
-
class
wrappyr.ctypes_api_builder.structure.Class(Node)¶ Class used to represent a Python class.
-
add_method(method)¶ Add
Methodinstance method to this class and set its parent to this node. This does not remove it from its current parent, so you must remove it from its parent first (if it has one of course).
-
add_member(member)¶ Add
Memberinstance member to this class and set its parent to this node. This does not remove it from its current parent, so you must remove it from its parent first (if it has one of course).
-
every_module() Returns all
Memberinstances that this class contains.
-
add_pointer(pointer)¶ Add
PointerTypeinstance pointer to this class and set its parent to this node. This does not remove it from its current parent, so you must remove it from its parent first (if it has one of course).Since a
PointerTypedoes not need to be a child of a Node to be used, this is probably only useful for loading from XML.
-
remove_pointer(pointer)¶ Remove
PointerTypeinstance pointer from this class and set its parent toNone.
-
every_pointer()¶ Returns all
PointerTypeinstances that this class contains.
-
-
class
wrappyr.ctypes_api_builder.structure.Function(Node)¶ Class used to represent a Python function.
You can create a
Functionfrom XML by using the <function> tag:<function name="func_a"> <call symbol="funcA"> <!-- A function makes calls to C functions. --> </call> </function>
-
class
wrappyr.ctypes_api_builder.structure.Method(Function)¶ Class used to represent a method of a Python class.
You can create a
Methodfrom XML by using the <method> tag:<class name="Test"> <method name="spam"> <!-- Python likes spam and eggs, C like foo and bar ;) --> <call symbol="foo" /> </method> </class>
-
class
wrappyr.ctypes_api_builder.structure.Member(Node)¶ Class used to represent a member Python of a class.
You can create a
Memberfrom XML by using the <member> tag and specify its getter and setter using the <getter> and <setter> tags respectively:<class name="Vector"> <member name="x"> <getter> <call symbol="Vector_GetX"> <returns type="ctypes.c_float" /> </call> </getter> <setter> <call symbol="Vector_SetX"> <argument type="ctypes.c_float" /> </call> </setter> </method> </class>
-
class
wrappyr.ctypes_api_builder.structure.Call(Node)¶ Class used to represent a call to a C function.
You can create a
Callfrom XML by using the <call> tag:<function name="take_over_the_world"> <!-- Obviously much more effecient in C, altough things will probably crash before anything useful happens. --> <call symbol="conquerWorld"> <argument name="timeout" type="ctypes.c_uint" /> <returns type="ctypes.c_bool"> </call> </function>
-
class
wrappyr.ctypes_api_builder.structure.Argument(Node)¶ Class used to represent an argument to a C function.
You can create an
Argumentinstance from XML by using the <argument> tag:<module name="module"> <class name="Class" /> <function name="func"> <call symbol="func"> <!-- You can reference existing classes by using full dotted paths --> <argument name="a" type="module.Class" /> <!-- You can also use ctypes.* types --> <argument name="b" type="ctypes.c_int" /> </call> </function> </module>
-
steals¶ Boolean value indicating whether passing an object as this argument to a call will steal the ownership over the object. Defaults to
False. See Memory management for more information.
-
invalidates¶ Boolean value indicating whether passing an object as this argument to a call will invalidate the object. Defaults to
False. See Memory management for more information.
-
-
class
wrappyr.ctypes_api_builder.structure.ReturnValue(Node)¶ Class used to represent the return value of a C function.
You can create a
ReturnValueinstance from XML by using the <returns> tag:<function name="func"> <call symbol="func"> <returns type="ctypes.c_float" /> </call> </function>
-
ownership¶ Boolean value indicating whether we have ownership over objects returned by this call. See Memory management for more information.
-
-
class
wrappyr.ctypes_api_builder.structure.CTypesStructure(Node)¶ The root of the structure that describes the Python API. This will contain the root
Packages andModules of the Python API.This class is represented by the XML tag <ctypes>. Since this is the root of the structure, this must also be the root node of the XML document:
<?xml version="1.0"?> <ctypes> <package name="MyPackage"> ... </package> </ctypes>
-
class
wrappyr.ctypes_api_builder.structure.CTypesStructureVisitor¶ A convenience class that takes a
CTypesStructureand calls visit_<class name> on itself for every node it finds. So as an example, it will call visit_Method(method) for every method it finds.Example:
class ClassPrinter(CTypesStructureVisitor): def visit_Class(cls): print "Found class: " + cls.name
-
process(node)¶ Will start at node and call the corresponding visit_* method for node and all of its descendents.
-
Examples¶
The recommended way to use this API is to process a CTypesStructure from within a wrappyr.Package. See Creating Package classes for an introduction on Packages. This section gives a few exmples of how you might preprocess a Python API.
Reorganize an API that doesn’t use namespaces into packages:
import wrappyr
from wrappyr.ctypes_api_builder.structure import Package
class Box2DPackage(wrappyr.Package):
CLASS_TO_PACKAGE = {
'b2Vec2': 'Box2D.common',
'b2Vec3': 'Box2D.common',
'b2Shape': 'Box2D.collision',
'b2PolygonShape': 'Box2D.collision',
'b2World': 'Box2D.dynamics',
'b2Body': 'Box2D.dynamics',
'b2Joint': 'Box2D.dynamic.joints',
'b2WeldJoint': 'Box2D.dynamic.joints',
}
def process_ctypes_structure(structure):
# Get all unique package paths
package_paths = set(self.CLASS_TO_PACKAGE.values())
# Sort by depth in tree
package_paths = sorted(packages, lambda name: name.count("."))
# Dictionary to hold all packages to be looked up by path
packages = {}
for path in package_paths:
last_dot = path.rfind(".")
# The name of the package is the part after the last dot
name = path[last_dot + 1 :]
package = Package(name)
packages[path] = package
# Add the package to its parent in the tree.
# We assume that the Box2D package already exists.
parent_path = path[: last_dot]
parent = structure.get_by_path(parent_path)
parent.add_package(package)
# Now we remove classes from their current parent and
# add them to their new package.
for class_name, package_path in self.CLASS_TO_PACKAGE.items():
cls = structure.get_by_path("Box2D." + class_name)
cls.parent.remove_class(cls)
packages[package_path].add_class(cls)
Convert camelCase method names into lowercase_with_underscores:
import re
import wrappyr
from wrappyr.ctypes_api_builder.structure import CTypesStructureVisitor
class CamelCaseTerminator(CTypesStructureVisitor):
def __init__(self):
self.to_rename = []
def visit_Method(self, method):
self.to_rename.append(method)
def terminate(self):
regex = re.compile(r'([a-z])([A-Z])')
to_underscore = lambda match: "%s_%s" % (match.group(1), match.group(2).lower())
for method in self.to_rename:
parent = method.parent
parent.remove_method(method)
method.name = regex.sub(to_underscore, method.name)
parent.add_method(method)
class MyPackage(wrappyr.Package):
def process_ctypes_structure(structure):
terminator = CamelCaseTerminator()
terminator.process(structure)
terminator.terminate()