|
""" This module is keeping track about API informations as well as |
providing some interface to easily access stored data |
""" |
|
import py |
import sys |
import types |
import inspect |
|
from py.__.apigen.tracer.description import FunctionDesc, ClassDesc, \ |
MethodDesc, Desc |
|
from py.__.apigen.tracer import model |
|
sorted = py.builtin.sorted |
|
def pkg_to_dict(module): |
defs = module.__pkg__.exportdefs |
d = {} |
for key, value in defs.iteritems(): |
chain = key.split('.') |
base = module |
try: |
for elem in chain: |
base = getattr(base, elem) |
except RuntimeError, exc: |
if elem == "greenlet": |
print exc.__class__.__name__, exc |
print "Greenlets not supported on this platform. Skipping apigen doc for this module" |
continue |
else: |
raise |
|
if value[1] == '*': |
d.update(get_star_import_tree(base, key)) |
else: |
d[key] = base |
return d |
|
def get_star_import_tree(module, modname): |
""" deal with '*' entries in an initpkg situation """ |
ret = {} |
modpath = py.path.local(inspect.getsourcefile(module)) |
pkgpath = module.__pkg__.getpath() |
for objname in dir(module): |
if objname.startswith('_'): |
continue |
obj = getattr(module, objname) |
if (isinstance(obj, types.ClassType) or |
isinstance(obj, types.ObjectType)): |
try: |
sourcefile_object = py.path.local( |
inspect.getsourcefile(obj)) |
except TypeError: |
continue |
else: |
if sourcefile_object.strpath != modpath.strpath: |
|
continue |
dotted_name = '%s.%s' % (modname, objname) |
ret[dotted_name] = obj |
return ret |
|
class DocStorage(object): |
""" Class storing info about API |
""" |
def __init__(self): |
self.module_name = None |
|
def consider_call(self, frame, caller_frame, upward_cut_frame=None): |
assert isinstance(frame, py.code.Frame) |
desc = self.find_desc(frame.code, frame.raw.f_locals) |
if desc: |
self.generalize_args(desc, frame) |
desc.consider_call_site(caller_frame, upward_cut_frame) |
desc.consider_start_locals(frame) |
|
def generalize_args(self, desc, frame): |
args = [arg for key, arg in frame.getargs()] |
|
desc.consider_call([model.guess_type(arg) for arg in args]) |
|
def generalize_retval(self, desc, arg): |
desc.consider_return(model.guess_type(arg)) |
|
def consider_return(self, frame, arg): |
assert isinstance(frame, py.code.Frame) |
desc = self.find_desc(frame.code, frame.raw.f_locals) |
if desc: |
self.generalize_retval(desc, arg) |
desc.consider_end_locals(frame) |
|
def consider_exception(self, frame, arg): |
desc = self.find_desc(frame.code, frame.raw.f_locals) |
if desc: |
exc_class, value, _ = arg |
desc.consider_exception(exc_class, value) |
|
def find_desc(self, code, locals): |
try: |
|
return self.desc_cache[(code.raw, |
locals[code.raw.co_varnames[0]].__class__)] |
except (KeyError, IndexError, AttributeError): |
return self.desc_cache.get(code.raw, None) |
|
|
|
|
|
def make_cache(self): |
self.desc_cache = {} |
for key, desc in self.descs.iteritems(): |
self.desc_cache[desc] = desc |
|
def from_dict(self, _dict, keep_frames=False, module_name=None): |
self.module_name = module_name |
self.descs = {} |
for key, val in _dict.iteritems(): |
to_key, to_val = self.make_desc(key, val) |
if to_key: |
self.descs[to_key] = to_val |
self.make_cache() |
|
return self |
|
|
|
|
def add_desc(self, name, value, **kwargs): |
key = name |
count = 1 |
while key in self.descs: |
key = "%s_%d" % (name, count) |
count += 1 |
key, desc = self.make_desc(key, value, **kwargs) |
if key: |
self.descs[key] = desc |
self.desc_cache[desc] = desc |
return desc |
else: |
return None |
|
def make_desc(self, key, value, add_desc=True, **kwargs): |
if isinstance(value, types.FunctionType): |
desc = FunctionDesc(key, value, **kwargs) |
elif isinstance(value, (types.ObjectType, types.ClassType)): |
desc = ClassDesc(key, value, **kwargs) |
|
|
for name in dir(value) + ['__init__']: |
field = getattr(value, name, None) |
if isinstance(field, types.MethodType) and \ |
isinstance(field.im_func, types.FunctionType): |
real_name = key + '.' + name |
md = MethodDesc(real_name, field) |
if add_desc: |
self.descs[real_name] = md |
desc.add_method_desc(name, md) |
|
elif isinstance(value, types.MethodType): |
desc = MethodDesc(key, value, **kwargs) |
else: |
desc = Desc(value) |
return (key, desc) |
|
|
def from_pkg(self, module, keep_frames=False): |
self.module = module |
self.from_dict(pkg_to_dict(module), keep_frames, module.__name__) |
|
return self |
|
def from_module(self, func): |
raise NotImplementedError("From module") |
|
class AbstractDocStorageAccessor(object): |
def __init__(self): |
raise NotImplementedError("Purely virtual object") |
|
def get_function_names(self): |
""" Returning names of all functions |
""" |
|
def get_class_names(self): |
""" Returning names of all classess |
""" |
|
def get_doc(self, name): |
""" Returning __doc__ of a function |
""" |
|
def get_function_definition(self, name): |
""" Returns definition of a function (source) |
""" |
|
def get_function_signature(self, name): |
""" Returns types of a function |
""" |
|
def get_function_callpoints(self, name): |
""" Returns list of all callpoints |
""" |
|
def get_module_name(self): |
pass |
|
def get_class_methods(self, name): |
""" Returns all methods of a class |
""" |
|
|
|
|
def get_module_info(self): |
""" Returns module information |
""" |
|
class DocStorageAccessor(AbstractDocStorageAccessor): |
""" Set of helper functions to access DocStorage, separated in different |
class to keep abstraction |
""" |
def __init__(self, ds): |
self.ds = ds |
|
def _get_names(self, filter): |
return [i for i, desc in self.ds.descs.iteritems() if filter(i, desc)] |
|
def get_function_names(self): |
return sorted(self._get_names(lambda i, desc: type(desc) is |
FunctionDesc)) |
|
def get_class_names(self): |
return sorted(self._get_names(lambda i, desc: isinstance(desc, |
ClassDesc))) |
|
|
|
|
def get_doc(self, name): |
return self.ds.descs[name].pyobj.__doc__ or "*Not documented*" |
|
def get_function_definition(self, name): |
desc = self.ds.descs[name] |
assert isinstance(desc, FunctionDesc) |
code = py.code.Code(desc.code) |
return code.fullsource[code.firstlineno] |
|
def get_function_signature(self, name): |
desc = self.ds.descs[name] |
|
names = desc.pyobj.func_code.co_varnames[ |
:desc.pyobj.func_code.co_argcount] |
types = desc.inputcells |
return zip(names, types), desc.retval |
|
def get_function_source(self, name): |
desc = self.ds.descs[name] |
try: |
return str(py.code.Source(desc.pyobj)) |
except IOError: |
return "Cannot get source" |
|
def get_function_callpoints(self, name): |
|
return self.ds.descs[name].get_call_sites() |
|
def get_function_local_changes(self, name): |
return self.ds.descs[name].get_local_changes() |
|
def get_function_exceptions(self, name): |
return sorted([i.__name__ for i in self.ds.descs[name].exceptions.keys()]) |
|
def get_module_name(self): |
if self.ds.module_name is not None: |
return self.ds.module_name |
elif hasattr(self.ds, 'module'): |
return self.ds.module.__name__ |
return "Unknown module" |
|
def get_class_methods(self, name): |
desc = self.ds.descs[name] |
assert isinstance(desc, ClassDesc) |
return sorted(desc.getfields()) |
|
def get_module_info(self): |
module = getattr(self.ds, 'module', None) |
if module is None: |
return "Lack of module info" |
try: |
retval = module.__doc__ or "*undocumented*" |
retval = module.__pkg__.description |
retval = module.__pkg__.long_description |
except AttributeError: |
pass |
return retval |
|
def get_type_desc(self, _type): |
|
if not isinstance(_type, model.SomeClass): |
return None |
|
for key, desc in self.ds.descs.iteritems(): |
if desc.pyobj == _type.cls: |
return key, 'class', desc.is_degenerated |
return None |
|
def get_method_origin(self, name): |
method = self.ds.descs[name].pyobj |
cls = method.im_class |
if not cls.__bases__: |
return self.desc_from_pyobj(cls, cls.__name__) |
curr = cls |
while curr: |
for base in curr.__bases__: |
basefunc = getattr(base, method.im_func.func_name, None) |
if (basefunc is not None and hasattr(basefunc, 'im_func') and |
hasattr(basefunc.im_func, 'func_code') and |
basefunc.im_func.func_code is |
method.im_func.func_code): |
curr = base |
break |
else: |
break |
return self.desc_from_pyobj(curr, curr.__name__) |
|
def get_possible_base_classes(self, name): |
cls = self.ds.descs[name].pyobj |
if not hasattr(cls, '__bases__'): |
return [] |
retval = [] |
for base in cls.__bases__: |
desc = self.desc_from_pyobj(base, base.__name__) |
if desc is not None: |
retval.append(desc) |
return retval |
|
def desc_from_pyobj(self, pyobj, name): |
for desc in self.ds.descs.values(): |
if isinstance(desc, ClassDesc) and desc.pyobj is pyobj: |
return desc |
|
key, desc = self.ds.make_desc(name, pyobj, False) |
|
desc.is_degenerated = True |
|
return desc |
|
def get_obj(self, name): |
return self.ds.descs[name].pyobj |
|
|