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
Method
to 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
Module
from XML by using the <module> tag:<module name="test"> ... </module>
-
find_library
([name])¶ Find
Library
with specified name in thisModule
or one of it’s parents. If name is not given, it will search for a library specified as default.
-
add_library
(library)¶ Add
Library
instance 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
Library
instance library from this module and set its parent toNone
.
-
add_class
(class)¶ Add
Class
instance 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
Function
instance 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
Function
instance function from this module and set its parent toNone
.
-
add_pointer
(pointer)¶ Add
PointerType
instance 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
PointerType
does 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
PointerType
instance pointer from this module and set its parent toNone
.
-
every_pointer
()¶ Returns all
PointerType
instances 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
Module
can contain, a package can contain:You can create a
Package
from XML by using the <package> tag:<package name="test"> ... </module>
-
add_package
(package)¶ Add
Package
instance 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
Package
instance 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
Node
it’s placed in (either aPackage
or aModule
) and all of its descendents.You can create a
Library
from 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
Method
instance 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
Member
instance 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
Member
instances that this class contains.
-
add_pointer
(pointer)¶ Add
PointerType
instance 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
PointerType
does 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
PointerType
instance pointer from this class and set its parent toNone
.
-
every_pointer
()¶ Returns all
PointerType
instances that this class contains.
-
-
class
wrappyr.ctypes_api_builder.structure.
Function
(Node)¶ Class used to represent a Python function.
You can create a
Function
from 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
Method
from 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
Member
from 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
Call
from 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
Argument
instance 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
ReturnValue
instance 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
Package
s andModule
s 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
CTypesStructure
and 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()