Viewing file: ApiFormatter.py (17.1 KB) -rw-r--r-- Select action/file-type: (+) | (+) | (+) | Code (+) | Session (+) | (+) | SDB (+) | (+) | (+) | (+) | (+) | (+) |
import os, re, pydoc, inspect, urllib, types, imp, stat, time import XmlFormatter
# Get the list of types to not document _builtin_types = vars(types).values() _builtin_types = [ t for t in _builtin_types if type(t) is types.TypeType ]
# Regex for extracting the argument list from a builtin function's docstring # WARNING - tuple arguments in the arglist will break this _re_arglist = re.compile(' *[a-zA-Z_][a-zA-Z0-9_]* *\((?P<arglist>[^)]*) *\)')
def _visiblename(name): """Decide whether to show documentation on a variable.""" # Certain special names are redundant. if name in ['__builtins__', '__doc__', '__file__', '__path__', '__module__', '__name__']: return 0 # Private names are hidden, but special names are displayed. if name.startswith('__') and name.endswith('__'): return 1 return not name.startswith('_')
class ApiFormatter(XmlFormatter.XmlFormatter):
def __init__(self, command, modules): XmlFormatter.XmlFormatter.__init__(self, command) self.documented_modules = modules return
def ispublic(self, name): if hasattr(self.module, '__all__'): return (name in self.module.__all__) and 'yes' or 'no' else: return (not name.startswith('_')) and 'yes' or 'no'
def isdocumented(self, name): return self.documented_modules.has_key(name) and 'yes' or 'no'
def doc_module(self, module): """ Produce documentation for a given module object. """ if not inspect.ismodule(module): raise TypeError("doc_module() requires a 'module' object " "but received a '%s'" % type(module).__name__)
attributes = {'name' : module.__name__} try: absfile = inspect.getabsfile(module) except TypeError: absfile = None else: attributes['file'] = urllib.pathname2url(absfile)
self.start_element('module', attributes)
if absfile: mtime = os.stat(absfile)[stat.ST_MTIME] mtime = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(mtime)) else: mtime = 'unknown' self.write_element('modification-date', content=mtime) self.write_description(module)
# standard module metadata for attr in ['author', 'credits', 'date', 'version']: if hasattr(module, '__%s__' % attr): content = self.escape(str(getattr(module, '__%s__' % attr))) self.write_element(attr, content=content)
modules = [] if hasattr(module, '__path__'): # Don't add the __init__ for this module (it IS this module) handled = {'__init__' : 1} submodules = [] path = module.__path__[0] for filename in os.listdir(path): modname = inspect.getmodulename(filename) if modname and not handled.get(modname): handled[modname] = 1 submodules.append(modname) elif pydoc.ispackage(os.path.join(path, filename)): # 'file' will be a bare directory name submodules.append(filename) for name in submodules: fullname = module.__name__ + '.' + name try: submod = pydoc.safeimport(fullname) except: submod = None if submod is None: # Could not import for some reason, create a stub submod = imp.new_module(fullname) modules.append((name, submod)) all = getattr(module, '__all__', [])
# Only document the modules listed in __all__. for name, member in inspect.getmembers(module, inspect.ismodule): if name in all: modules.append((name, member)) if modules: modules.sort() self.section('modules', modules, self.doc_submodule)
# Only document the classes defined in this module or # are listed in __all__. classes = [ t for t in inspect.getmembers(module, inspect.isclass) if ((inspect.getmodule(t[1]) or module) is module or t[0] in all) ] if classes: self.section('classes', classes, self.doc_class)
# Only document the functions defined in this module or # are listed in __all__. funcs = [ t for t in inspect.getmembers(module, inspect.isroutine) if ((inspect.getmodule(t[1]) or module) is module or t[0] in all) ] if funcs: self.section('functions', funcs, self.doc_function)
# Only document the "visible" objects or those listed in __all__. fields = [ t for t in inspect.getmembers(module, pydoc.isdata) if t[0] in all or _visiblename(t[0]) ] if fields: self.section('fields', fields, self.doc_field)
self.end_element('module') return
def doc_submodule(self, module, name): """Produce XML documentation for a submodule""" realname = module.__name__ name = name or realname
attributes = {'name' : name, 'realname' : realname, 'public' : self.ispublic(name), 'documented' : self.isdocumented(realname), }
self.start_element('module-reference', attributes)
self.write_description(module)
self.end_element('module-reference') return
def doc_class(self, klass, name): """Produce XML documentation for a given class object.""" realname = klass.__name__ name = name or realname
attributes = {'name' : name, 'public' : self.ispublic(name), } if name != realname: attributes['realname'] = realname
self.start_element('class', attributes)
self.start_element('bases') for base in klass.__bases__: attributes = { 'class' : base.__name__, 'module' : base.__module__, 'documented' : self.isdocumented(base.__module__), } self.write_element('base', attributes) self.end_element('bases')
self.write_description(klass)
self.start_element('method-resolution-order') mro = list(inspect.getmro(klass)) bases = {} for base in mro: attributes = {'module' : base.__module__, 'name' : base.__name__, } self.write_element('base', attributes) self.end_element('method-resolution-order')
attrs = inspect.classify_class_attrs(klass) attrs = [ t for t in attrs if _visiblename(t[0]) ] while attrs: if mro: thisclass = mro.pop(0) else: thisclass = attrs[0][2]
inherited_attrs = [ t for t in attrs if t[2] is not thisclass ] attrs = [ t for t in attrs if t[2] is thisclass ]
# Sort attrs by name (which is the first item in the tuple) attrs.sort()
# Group the attributes into 'methods' and 'fields', where: # methods = the method types and functions from 'data' # fields = properties and the non-functions from 'data' methods = [] fields = [] for info in attrs: if info[1].endswith('method') or inspect.isbuiltin(info[3]): methods.append(info) else: fields.append(info)
inherited = thisclass is not klass attributes = { 'class' : thisclass.__name__, 'module' : thisclass.__module__, 'documented' : self.isdocumented(thisclass.__module__), 'inherited' : inherited and 'yes' or 'no', }
if methods: self.start_element('methods', attributes) for name, kind, home_class, method in methods: if inherited: self.doc_inherited(name) else: if inspect.isbuiltin(method): self.doc_function(method, name) else: # inspect.classify_class_attrs gets the methods # from the class __dict__ which returns functions, # not methods method = getattr(klass, name) self.doc_method(method, name, klass, kind) self.end_element('methods')
if fields: self.start_element('fields', attributes) for name, kind, home_class, field in fields: if inherited: self.doc_inherited(name) else: # inspect.classify_class_attrs gets the attributes from # the class __dict__ #other = getattr(klass, name) self.doc_field(field, name, klass) self.end_element('fields')
attrs = inherited_attrs
self.end_element('class') return
def format_arg(self, arg, default=None): attributes = {} if default is not None: attributes['default'] = default if type(arg) in [types.TupleType, types.ListType]: self.start_element('sequence', attributes) for a in arg: self.format_arg(a) self.end_element('sequence') else: attributes['name'] = arg self.write_element('arg', attributes) return
def doc_arguments(self, object): self.start_element('arguments')
if inspect.isfunction(object): args, varargs, varkw, defaults = inspect.getargspec(object)
if defaults: firstdefault = len(args) - len(defaults)
for i in xrange(len(args)): if defaults and i >= firstdefault: default = repr(defaults[i - firstdefault]) else: default = None self.format_arg(args[i], default)
if varargs: self.write_element('var-args', {'name' : varargs})
if varkw: self.write_element('var-keywords', {'name' : varkw}) else: arglist = '...' if inspect.isbuiltin(object): # Extract argument list from docstring match = _re_arglist.match(pydoc.getdoc(object)) if match: arglist = match.group('arglist') self.write_element('unknown', content=arglist)
self.end_element('arguments') return
def doc_method(self, method, name, klass, kind): """ Document a method, class method or static method as given by 'kind' """ realname = method.__name__ name = name or realname attributes = {'name' : name, 'id' : klass.__name__ + '-' + name, 'public' : self.ispublic(name), } if name != realname: attributes['realname'] = realname # aliased methods fail the 'is' test but compare true if getattr(klass, realname, None) == method: attributes['realid'] = klass.__name__ + '-' + realname
tagname = kind.replace(' ', '-') self.start_element(tagname, attributes)
self.write_description(method)
if inspect.ismethod(method): self.doc_arguments(method.im_func) else: self.doc_arguments(method)
# In Python 2.2 it could also be a method descriptor which is # just a wrapper that acts like a method if inspect.ismethod(method): imclass = method.im_class if imclass is not klass: attributes = {'module' : imclass.__module__, 'name' : imclass.__name__, } self.write_element('inherited-from', attributes) elif imclass: # This always returns the passed in class as the first item for base in inspect.getmro(imclass)[1:]: overridden = getattr(base, name, None) if overridden: attributes = {'module' : base.__module__, 'name' : base.__name__, } self.write_element('overrides', attributes) break
self.end_element(tagname) return
def doc_function(self, func, name): """ Document a function """ realname = func.__name__ if realname == '<lambda>': realname = 'lambda' name = name or realname
attributes = {'name' : name, 'id' : name, 'public' : self.ispublic(name), } if name != realname: attributes['realname'] = realname
self.start_element('function', attributes)
self.write_description(func)
self.doc_arguments(func)
self.end_element('function') return
def doc_field(self, object, name, klass=None): """Produce XML documentation for a data object.""" if klass: field_id = klass.__name__ + '-' + name else: field_id = name attributes = {'name' : name, 'id' : field_id, 'public' : self.ispublic(name), } self.start_element('field', attributes)
# SWIG wrapped objects throw exceptions with getattr(obj, attr, None) # but not with hasattr(obj, attr) if ((isinstance(object, types.InstanceType) and object.__doc__ != object.__class__.__doc__) or (type(object) not in _builtin_types and hasattr(object, '__doc__') and getattr(object, '__doc__')) ): self.write_description(object)
self.write_element('value', content=self.repr(object))
self.end_element('field') return
def doc_inherited(self, name): """Produce XML documentation for an inherited object.""" attributes = {'name' : name, 'public' : self.ispublic(name), } self.write_element('member-reference', attributes) return
def write_description(self, object): """Produce XML tag(s) for an object description.""" docstring = self.escape(pydoc.getdoc(object)) paragraphs = docstring.split('\n\n') if paragraphs: abstract = paragraphs[0] description = '\n\n'.join(paragraphs[1:]) else: abstract = None description = None
self.write_element('abstract', content=abstract) self.write_element('description', content=description) return
# function not available in Python 2.1 if not hasattr(inspect, 'getmro'): def _searchbases(cls, accum): # Simulate the "classic class" search order. if cls in accum: return accum.append(cls) for base in cls.__bases__: _searchbases(base, accum)
def getmro(cls): "Return list of base classes (including cls) in method resolution order." result = [] _searchbases(cls, result) return result inspect.getmro = getmro
# function not available in Python 2.1 if not hasattr(inspect, 'classify_class_attrs'): def classify_class_attrs(cls): """Return list of attribute-descriptor tuples.
For each name defined on class, cls, the return list contains a 4-tuple with these elements:
0. The name (a string).
1. The kind of attribute this is, one of these strings: 'method' any flavor of method 'data' not a method
2. The class which defined this attribute (a class).
3. The object as obtained directly from the defining class's __dict__, not via getattr. """
# This mimics a Python 2.2 dir(), which returns an alphabetized list # of all reachable attribute names. bases = getmro(cls) bases.reverse() combined = {} for baseclass in bases: for name, value in baseclass.__dict__.items(): combined[name] = (baseclass, value) names = combined.keys() names.sort()
result = [] for name in names: true_class, obj = combined[name] # Classify the object. if inspect.ismethod(getattr(cls, name)): kind = "method" else: kind = "data" result.append((name, kind, true_class, obj)) return result inspect.classify_class_attrs = classify_class_attrs
|