| # epydoc -- Introspection |
| # |
| # Copyright (C) 2005 Edward Loper |
| # Author: Edward Loper <[email protected]> |
| # URL: <http://epydoc.sf.net> |
| # |
| # $Id: docintrospecter.py 1678 2008-01-29 17:21:29Z edloper $ |
| |
| """ |
| Extract API documentation about python objects by directly introspecting |
| their values. |
| |
| The function L{introspect_docs()}, which provides the main interface |
| of this module, examines a Python objects via introspection, and uses |
| the information it finds to create an L{APIDoc} objects containing the |
| API documentation for that objects. |
| |
| The L{register_introspecter()} method can be used to extend the |
| functionality of C{docintrospector}, by providing methods that handle |
| special value types. |
| """ |
| __docformat__ = 'epytext en' |
| |
| ###################################################################### |
| ## Imports |
| ###################################################################### |
| |
| import inspect, re, sys, os.path, imp |
| # API documentation encoding: |
| from epydoc.apidoc import * |
| # Type comparisons: |
| from types import * |
| # Error reporting: |
| from epydoc import log |
| # Helper functions: |
| from epydoc.util import * |
| # For extracting encoding for docstrings: |
| import epydoc.docparser |
| # Builtin values |
| import __builtin__ |
| # Backwards compatibility |
| from epydoc.compat import * |
| |
| ###################################################################### |
| ## Caches |
| ###################################################################### |
| |
| _valuedoc_cache = {} |
| """A cache containing the API documentation for values that we've |
| already seen. This cache is implemented as a dictionary that maps a |
| value's pyid to its L{ValueDoc}. |
| |
| Note that if we encounter a value but decide not to introspect it |
| (because it's imported from another module), then C{_valuedoc_cache} |
| will contain an entry for the value, but the value will not be listed |
| in L{_introspected_values}.""" |
| |
| _introspected_values = {} |
| """A record which values we've introspected, encoded as a dictionary from |
| pyid to C{bool}.""" |
| |
| def clear_cache(): |
| """ |
| Discard any cached C{APIDoc} values that have been computed for |
| introspected values. |
| """ |
| _valuedoc_cache.clear() |
| _introspected_values.clear() |
| |
| ###################################################################### |
| ## Introspection |
| ###################################################################### |
| |
| def introspect_docs(value=None, name=None, filename=None, context=None, |
| is_script=False, module_name=None): |
| """ |
| Generate the API documentation for a specified object by |
| introspecting Python values, and return it as a L{ValueDoc}. The |
| object to generate documentation for may be specified using |
| the C{value} parameter, the C{filename} parameter, I{or} the |
| C{name} parameter. (It is an error to specify more than one |
| of these three parameters, or to not specify any of them.) |
| |
| @param value: The python object that should be documented. |
| @param filename: The name of the file that contains the python |
| source code for a package, module, or script. If |
| C{filename} is specified, then C{introspect} will return a |
| C{ModuleDoc} describing its contents. |
| @param name: The fully-qualified python dotted name of any |
| value (including packages, modules, classes, and |
| functions). C{DocParser} will automatically figure out |
| which module(s) it needs to import in order to find the |
| documentation for the specified object. |
| @param context: The API documentation for the class of module |
| that contains C{value} (if available). |
| @param module_name: The name of the module where the value is defined. |
| Useful to retrieve the docstring encoding if there is no way to |
| detect the module by introspection (such as in properties) |
| """ |
| if value is None and name is not None and filename is None: |
| value = get_value_from_name(DottedName(name)) |
| elif value is None and name is None and filename is not None: |
| if is_script: |
| value = get_value_from_scriptname(filename) |
| else: |
| value = get_value_from_filename(filename, context) |
| elif name is None and filename is None: |
| # it's ok if value is None -- that's a value, after all. |
| pass |
| else: |
| raise ValueError("Expected exactly one of the following " |
| "arguments: value, name, filename") |
| |
| pyid = id(value) |
| |
| # If we've already introspected this value, then simply return |
| # its ValueDoc from our cache. |
| if pyid in _introspected_values: |
| # If the file is a script, then adjust its name. |
| if is_script and filename is not None: |
| _valuedoc_cache[pyid].canonical_name = DottedName( |
| munge_script_name(str(filename))) |
| return _valuedoc_cache[pyid] |
| |
| # Create an initial value doc for this value & add it to the cache. |
| val_doc = _get_valuedoc(value) |
| |
| # Introspect the value. |
| _introspected_values[pyid] = True |
| introspect_func = _get_introspecter(value) |
| introspect_func(value, val_doc, module_name=module_name) |
| |
| # Set canonical name, if it was given |
| if val_doc.canonical_name is UNKNOWN and name is not None: |
| val_doc.canonical_name = DottedName(name) |
| |
| # If the file is a script, then adjust its name. |
| if is_script and filename is not None: |
| val_doc.canonical_name = DottedName(munge_script_name(str(filename))) |
| |
| if val_doc.canonical_name is UNKNOWN and filename is not None: |
| shadowed_name = DottedName(value.__name__) |
| log.warning("Module %s is shadowed by a variable with " |
| "the same name." % shadowed_name) |
| val_doc.canonical_name = DottedName(str(shadowed_name)+"'") |
| |
| return val_doc |
| |
| def _get_valuedoc(value): |
| """ |
| If a C{ValueDoc} for the given value exists in the valuedoc |
| cache, then return it; otherwise, create a new C{ValueDoc}, |
| add it to the cache, and return it. When possible, the new |
| C{ValueDoc}'s C{pyval}, C{repr}, and C{canonical_name} |
| attributes will be set appropriately. |
| """ |
| pyid = id(value) |
| val_doc = _valuedoc_cache.get(pyid) |
| if val_doc is None: |
| try: canonical_name = get_canonical_name(value, strict=True) |
| except DottedName.InvalidDottedName: canonical_name = UNKNOWN |
| val_doc = ValueDoc(pyval=value, canonical_name = canonical_name, |
| docs_extracted_by='introspecter') |
| _valuedoc_cache[pyid] = val_doc |
| |
| # If it's a module, then do some preliminary introspection. |
| # Otherwise, check what the containing module is (used e.g. |
| # to decide what markup language should be used for docstrings) |
| if inspect.ismodule(value): |
| introspect_module(value, val_doc, preliminary=True) |
| val_doc.defining_module = val_doc |
| else: |
| module_name = str(get_containing_module(value)) |
| module = sys.modules.get(module_name) |
| if module is not None and inspect.ismodule(module): |
| val_doc.defining_module = _get_valuedoc(module) |
| |
| return val_doc |
| |
| #//////////////////////////////////////////////////////////// |
| # Module Introspection |
| #//////////////////////////////////////////////////////////// |
| |
| #: A list of module variables that should not be included in a |
| #: module's API documentation. |
| UNDOCUMENTED_MODULE_VARS = ( |
| '__builtins__', '__doc__', '__all__', '__file__', '__path__', |
| '__name__', '__extra_epydoc_fields__', '__docformat__') |
| |
| def introspect_module(module, module_doc, module_name=None, preliminary=False): |
| """ |
| Add API documentation information about the module C{module} |
| to C{module_doc}. |
| """ |
| module_doc.specialize_to(ModuleDoc) |
| |
| # Record the module's docformat |
| if hasattr(module, '__docformat__'): |
| module_doc.docformat = unicode(module.__docformat__) |
| |
| # Record the module's filename |
| if hasattr(module, '__file__'): |
| try: module_doc.filename = unicode(module.__file__) |
| except KeyboardInterrupt: raise |
| except: pass |
| if module_doc.filename is not UNKNOWN: |
| try: module_doc.filename = py_src_filename(module_doc.filename) |
| except ValueError: pass |
| |
| # If this is just a preliminary introspection, then don't do |
| # anything else. (Typically this is true if this module was |
| # imported, but is not included in the set of modules we're |
| # documenting.) |
| module_doc.variables = {} |
| if preliminary: return |
| |
| # Record the module's docstring |
| if hasattr(module, '__doc__'): |
| module_doc.docstring = get_docstring(module) |
| |
| # If the module has a __path__, then it's (probably) a |
| # package; so set is_package=True and record its __path__. |
| if hasattr(module, '__path__'): |
| module_doc.is_package = True |
| try: module_doc.path = [unicode(p) for p in module.__path__] |
| except KeyboardInterrupt: raise |
| except: pass |
| else: |
| module_doc.is_package = False |
| |
| # Make sure we have a name for the package. |
| dotted_name = module_doc.canonical_name |
| if dotted_name is UNKNOWN: |
| dotted_name = DottedName(module.__name__) |
| name_without_primes = DottedName(str(dotted_name).replace("'", "")) |
| |
| # Record the module's parent package, if it has one. |
| if len(dotted_name) > 1: |
| package_name = str(dotted_name.container()) |
| package = sys.modules.get(package_name) |
| if package is not None: |
| module_doc.package = introspect_docs(package) |
| else: |
| module_doc.package = None |
| |
| # Initialize the submodules property |
| module_doc.submodules = [] |
| |
| # Add the module to its parent package's submodules list. |
| if module_doc.package not in (None, UNKNOWN): |
| module_doc.package.submodules.append(module_doc) |
| |
| # Look up the module's __all__ attribute (public names). |
| public_names = None |
| if hasattr(module, '__all__'): |
| try: |
| public_names = set([str(name) for name in module.__all__]) |
| except KeyboardInterrupt: raise |
| except: pass |
| |
| # Record the module's variables. |
| module_doc.variables = {} |
| for child_name in dir(module): |
| if child_name in UNDOCUMENTED_MODULE_VARS: continue |
| child = getattr(module, child_name) |
| |
| # Create a VariableDoc for the child, and introspect its |
| # value if it's defined in this module. |
| container = get_containing_module(child) |
| if ((container is not None and |
| container == name_without_primes) or |
| (public_names is not None and |
| child_name in public_names)): |
| # Local variable. |
| child_val_doc = introspect_docs(child, context=module_doc, |
| module_name=dotted_name) |
| child_var_doc = VariableDoc(name=child_name, |
| value=child_val_doc, |
| is_imported=False, |
| container=module_doc, |
| docs_extracted_by='introspecter') |
| elif container is None or module_doc.canonical_name is UNKNOWN: |
| |
| # Don't introspect stuff "from __future__" |
| if is_future_feature(child): continue |
| |
| # Possibly imported variable. |
| child_val_doc = introspect_docs(child, context=module_doc) |
| child_var_doc = VariableDoc(name=child_name, |
| value=child_val_doc, |
| container=module_doc, |
| docs_extracted_by='introspecter') |
| else: |
| # Imported variable. |
| child_val_doc = _get_valuedoc(child) |
| child_var_doc = VariableDoc(name=child_name, |
| value=child_val_doc, |
| is_imported=True, |
| container=module_doc, |
| docs_extracted_by='introspecter') |
| |
| # If the module's __all__ attribute is set, use it to set the |
| # variables public/private status and imported status. |
| if public_names is not None: |
| if child_name in public_names: |
| child_var_doc.is_public = True |
| if not isinstance(child_var_doc, ModuleDoc): |
| child_var_doc.is_imported = False |
| else: |
| child_var_doc.is_public = False |
| |
| module_doc.variables[child_name] = child_var_doc |
| |
| return module_doc |
| |
| #//////////////////////////////////////////////////////////// |
| # Class Introspection |
| #//////////////////////////////////////////////////////////// |
| |
| #: A list of class variables that should not be included in a |
| #: class's API documentation. |
| UNDOCUMENTED_CLASS_VARS = ( |
| '__doc__', '__module__', '__dict__', '__weakref__', '__slots__', |
| '__pyx_vtable__') |
| |
| def introspect_class(cls, class_doc, module_name=None): |
| """ |
| Add API documentation information about the class C{cls} |
| to C{class_doc}. |
| """ |
| class_doc.specialize_to(ClassDoc) |
| |
| # Record the class's docstring. |
| class_doc.docstring = get_docstring(cls) |
| |
| # Record the class's __all__ attribute (public names). |
| public_names = None |
| if hasattr(cls, '__all__'): |
| try: |
| public_names = set([str(name) for name in cls.__all__]) |
| except KeyboardInterrupt: raise |
| except: pass |
| |
| # Start a list of subclasses. |
| class_doc.subclasses = [] |
| |
| # Sometimes users will define a __metaclass__ that copies all |
| # class attributes from bases directly into the derived class's |
| # __dict__ when the class is created. (This saves the lookup time |
| # needed to search the base tree for an attribute.) But for the |
| # docs, we only want to list these copied attributes in the |
| # parent. So only add an attribute if it is not identical to an |
| # attribute of a base class. (Unfortunately, this can sometimes |
| # cause an attribute to look like it was inherited, even though it |
| # wasn't, if it happens to have the exact same value as the |
| # corresponding base's attribute.) An example of a case where |
| # this helps is PyQt -- subclasses of QWidget get about 300 |
| # methods injected into them. |
| base_children = {} |
| |
| # Record the class's base classes; and add the class to its |
| # base class's subclass lists. |
| if hasattr(cls, '__bases__'): |
| try: bases = list(cls.__bases__) |
| except: |
| bases = None |
| log.warning("Class '%s' defines __bases__, but it does not " |
| "contain an iterable; ignoring base list." |
| % getattr(cls, '__name__', '??')) |
| if bases is not None: |
| class_doc.bases = [] |
| for base in bases: |
| basedoc = introspect_docs(base) |
| class_doc.bases.append(basedoc) |
| basedoc.subclasses.append(class_doc) |
| |
| bases.reverse() |
| for base in bases: |
| if hasattr(base, '__dict__'): |
| base_children.update(base.__dict__) |
| |
| # The module name is not defined if the class is being introspected |
| # as another class base. |
| if module_name is None and class_doc.defining_module not in (None, UNKNOWN): |
| module_name = class_doc.defining_module.canonical_name |
| |
| # Record the class's local variables. |
| class_doc.variables = {} |
| if hasattr(cls, '__dict__'): |
| private_prefix = '_%s__' % getattr(cls, '__name__', '<none>') |
| for child_name, child in cls.__dict__.items(): |
| if (child_name in base_children |
| and base_children[child_name] == child): |
| continue |
| |
| if child_name.startswith(private_prefix): |
| child_name = child_name[len(private_prefix)-2:] |
| if child_name in UNDOCUMENTED_CLASS_VARS: continue |
| val_doc = introspect_docs(child, context=class_doc, |
| module_name=module_name) |
| var_doc = VariableDoc(name=child_name, value=val_doc, |
| container=class_doc, |
| docs_extracted_by='introspecter') |
| if public_names is not None: |
| var_doc.is_public = (child_name in public_names) |
| class_doc.variables[child_name] = var_doc |
| |
| return class_doc |
| |
| #//////////////////////////////////////////////////////////// |
| # Routine Introspection |
| #//////////////////////////////////////////////////////////// |
| |
| def introspect_routine(routine, routine_doc, module_name=None): |
| """Add API documentation information about the function |
| C{routine} to C{routine_doc} (specializing it to C{Routine_doc}).""" |
| routine_doc.specialize_to(RoutineDoc) |
| |
| # Extract the underying function |
| if isinstance(routine, MethodType): |
| func = routine.im_func |
| elif isinstance(routine, staticmethod): |
| func = routine.__get__(0) |
| elif isinstance(routine, classmethod): |
| func = routine.__get__(0).im_func |
| else: |
| func = routine |
| |
| # Record the function's docstring. |
| routine_doc.docstring = get_docstring(func) |
| |
| # Record the function's signature. |
| if isinstance(func, FunctionType): |
| (args, vararg, kwarg, defaults) = inspect.getargspec(func) |
| |
| # Add the arguments. |
| routine_doc.posargs = args |
| routine_doc.vararg = vararg |
| routine_doc.kwarg = kwarg |
| |
| # Set default values for positional arguments. |
| routine_doc.posarg_defaults = [None]*len(args) |
| if defaults is not None: |
| offset = len(args)-len(defaults) |
| for i in range(len(defaults)): |
| default_val = introspect_docs(defaults[i]) |
| routine_doc.posarg_defaults[i+offset] = default_val |
| |
| # If it's a bound method, then strip off the first argument. |
| if isinstance(routine, MethodType) and routine.im_self is not None: |
| routine_doc.posargs = routine_doc.posargs[1:] |
| routine_doc.posarg_defaults = routine_doc.posarg_defaults[1:] |
| |
| # Set the routine's line number. |
| if hasattr(func, 'func_code'): |
| routine_doc.lineno = func.func_code.co_firstlineno |
| |
| else: |
| # [XX] I should probably use UNKNOWN here?? |
| # dvarrazzo: if '...' is to be changed, also check that |
| # `docstringparser.process_arg_field()` works correctly. |
| # See SF bug #1556024. |
| routine_doc.posargs = ['...'] |
| routine_doc.posarg_defaults = [None] |
| routine_doc.kwarg = None |
| routine_doc.vararg = None |
| |
| # Change type, if appropriate. |
| if isinstance(routine, staticmethod): |
| routine_doc.specialize_to(StaticMethodDoc) |
| if isinstance(routine, classmethod): |
| routine_doc.specialize_to(ClassMethodDoc) |
| |
| return routine_doc |
| |
| #//////////////////////////////////////////////////////////// |
| # Property Introspection |
| #//////////////////////////////////////////////////////////// |
| |
| def introspect_property(prop, prop_doc, module_name=None): |
| """Add API documentation information about the property |
| C{prop} to C{prop_doc} (specializing it to C{PropertyDoc}).""" |
| prop_doc.specialize_to(PropertyDoc) |
| |
| # Record the property's docstring. |
| prop_doc.docstring = get_docstring(prop, module_name=module_name) |
| |
| # Record the property's access functions. |
| if hasattr(prop, 'fget'): |
| prop_doc.fget = introspect_docs(prop.fget) |
| prop_doc.fset = introspect_docs(prop.fset) |
| prop_doc.fdel = introspect_docs(prop.fdel) |
| |
| return prop_doc |
| |
| #//////////////////////////////////////////////////////////// |
| # Generic Value Introspection |
| #//////////////////////////////////////////////////////////// |
| |
| def introspect_other(val, val_doc, module_name=None): |
| """Specialize val_doc to a C{GenericValueDoc} and return it.""" |
| val_doc.specialize_to(GenericValueDoc) |
| return val_doc |
| |
| #//////////////////////////////////////////////////////////// |
| # Helper functions |
| #//////////////////////////////////////////////////////////// |
| |
| def isclass(object): |
| """ |
| Return true if the given object is a class. In particular, return |
| true if object is an instance of C{types.TypeType} or of |
| C{types.ClassType}. This is used instead of C{inspect.isclass()}, |
| because the latter returns true for objects that are not classes |
| (in particular, it returns true for any object that has a |
| C{__bases__} attribute, including objects that define |
| C{__getattr__} to always return a value). |
| """ |
| return isinstance(object, tuple(_CLASS_TYPES)) |
| |
| _CLASS_TYPES = set([TypeType, ClassType]) |
| """A list of types that should be treated as classes.""" |
| |
| def register_class_type(typ): |
| """Add a type to the lists of types that should be treated as |
| classes. By default, this list contains C{TypeType} and |
| C{ClassType}.""" |
| _CLASS_TYPES.add(typ) |
| |
| __future_check_works = None |
| |
| def is_future_feature(object): |
| """ |
| Return True if C{object} results from a C{from __future__ import feature} |
| statement. |
| """ |
| # Guard from unexpected implementation changes of the __future__ module. |
| global __future_check_works |
| if __future_check_works is not None: |
| if __future_check_works: |
| import __future__ |
| return isinstance(object, __future__._Feature) |
| else: |
| return False |
| else: |
| __future_check_works = True |
| try: |
| return is_future_feature(object) |
| except: |
| __future_check_works = False |
| log.warning("Troubles inspecting __future__. Python implementation" |
| " may have been changed.") |
| return False |
| |
| def get_docstring(value, module_name=None): |
| """ |
| Return the docstring for the given value; or C{None} if it |
| does not have a docstring. |
| @rtype: C{unicode} |
| """ |
| docstring = getattr(value, '__doc__', None) |
| if docstring is None: |
| return None |
| elif isinstance(docstring, unicode): |
| return docstring |
| elif isinstance(docstring, str): |
| try: return unicode(docstring, 'ascii') |
| except UnicodeDecodeError: |
| if module_name is None: |
| module_name = get_containing_module(value) |
| if module_name is not None: |
| try: |
| module = get_value_from_name(module_name) |
| filename = py_src_filename(module.__file__) |
| encoding = epydoc.docparser.get_module_encoding(filename) |
| return unicode(docstring, encoding) |
| except KeyboardInterrupt: raise |
| except Exception: pass |
| if hasattr(value, '__name__'): name = value.__name__ |
| else: name = repr(value) |
| log.warning("%s's docstring is not a unicode string, but it " |
| "contains non-ascii data -- treating it as " |
| "latin-1." % name) |
| return unicode(docstring, 'latin-1') |
| return None |
| elif value is BuiltinMethodType: |
| # Don't issue a warning for this special case. |
| return None |
| else: |
| if hasattr(value, '__name__'): name = value.__name__ |
| else: name = repr(value) |
| log.warning("%s's docstring is not a string -- ignoring it." % |
| name) |
| return None |
| |
| def get_canonical_name(value, strict=False): |
| """ |
| @return: the canonical name for C{value}, or C{UNKNOWN} if no |
| canonical name can be found. Currently, C{get_canonical_name} |
| can find canonical names for: modules; functions; non-nested |
| classes; methods of non-nested classes; and some class methods |
| of non-nested classes. |
| |
| @rtype: L{DottedName} or C{UNKNOWN} |
| """ |
| if not hasattr(value, '__name__'): return UNKNOWN |
| |
| # Get the name via introspection. |
| if isinstance(value, ModuleType): |
| try: |
| dotted_name = DottedName(value.__name__, strict=strict) |
| # If the module is shadowed by a variable in its parent |
| # package(s), then add a prime mark to the end, to |
| # differentiate it from the variable that shadows it. |
| if verify_name(value, dotted_name) is UNKNOWN: |
| log.warning("Module %s is shadowed by a variable with " |
| "the same name." % dotted_name) |
| # Note -- this return bypasses verify_name check: |
| return DottedName(value.__name__+"'") |
| except DottedName.InvalidDottedName: |
| # Name is not a valid Python identifier -- treat as script. |
| if hasattr(value, '__file__'): |
| filename = '%s' % value.__str__ |
| dotted_name = DottedName(munge_script_name(filename)) |
| |
| elif isclass(value): |
| if value.__module__ == '__builtin__': |
| dotted_name = DottedName(value.__name__, strict=strict) |
| else: |
| dotted_name = DottedName(value.__module__, value.__name__, |
| strict=strict) |
| |
| elif (inspect.ismethod(value) and value.im_self is not None and |
| value.im_class is ClassType and |
| not value.__name__.startswith('<')): # class method. |
| class_name = get_canonical_name(value.im_self) |
| if class_name is UNKNOWN: return UNKNOWN |
| dotted_name = DottedName(class_name, value.__name__, strict=strict) |
| elif (inspect.ismethod(value) and |
| not value.__name__.startswith('<')): |
| class_name = get_canonical_name(value.im_class) |
| if class_name is UNKNOWN: return UNKNOWN |
| dotted_name = DottedName(class_name, value.__name__, strict=strict) |
| elif (isinstance(value, FunctionType) and |
| not value.__name__.startswith('<')): |
| module_name = _find_function_module(value) |
| if module_name is None: return UNKNOWN |
| dotted_name = DottedName(module_name, value.__name__, strict=strict) |
| else: |
| return UNKNOWN |
| |
| return verify_name(value, dotted_name) |
| |
| def verify_name(value, dotted_name): |
| """ |
| Verify the name. E.g., if it's a nested class, then we won't be |
| able to find it with the name we constructed. |
| """ |
| if dotted_name is UNKNOWN: return UNKNOWN |
| if len(dotted_name) == 1 and hasattr(__builtin__, dotted_name[0]): |
| return dotted_name |
| named_value = sys.modules.get(dotted_name[0]) |
| if named_value is None: return UNKNOWN |
| for identifier in dotted_name[1:]: |
| try: named_value = getattr(named_value, identifier) |
| except: return UNKNOWN |
| if value is named_value: |
| return dotted_name |
| else: |
| return UNKNOWN |
| |
| # [xx] not used: |
| def value_repr(value): |
| try: |
| s = '%r' % value |
| if isinstance(s, str): |
| s = decode_with_backslashreplace(s) |
| return s |
| except: |
| return UNKNOWN |
| |
| def get_containing_module(value): |
| """ |
| Return the name of the module containing the given value, or |
| C{None} if the module name can't be determined. |
| @rtype: L{DottedName} |
| """ |
| if inspect.ismodule(value): |
| return DottedName(value.__name__) |
| elif isclass(value): |
| return DottedName(value.__module__) |
| elif (inspect.ismethod(value) and value.im_self is not None and |
| value.im_class is ClassType): # class method. |
| return DottedName(value.im_self.__module__) |
| elif inspect.ismethod(value): |
| return DottedName(value.im_class.__module__) |
| elif inspect.isroutine(value): |
| module = _find_function_module(value) |
| if module is None: return None |
| return DottedName(module) |
| else: |
| return None |
| |
| def _find_function_module(func): |
| """ |
| @return: The module that defines the given function. |
| @rtype: C{module} |
| @param func: The function whose module should be found. |
| @type func: C{function} |
| """ |
| if hasattr(func, '__module__'): |
| return func.__module__ |
| try: |
| module = inspect.getmodule(func) |
| if module: return module.__name__ |
| except KeyboardInterrupt: raise |
| except: pass |
| |
| # This fallback shouldn't usually be needed. But it is needed in |
| # a couple special cases (including using epydoc to document |
| # itself). In particular, if a module gets loaded twice, using |
| # two different names for the same file, then this helps. |
| for module in sys.modules.values(): |
| if (hasattr(module, '__dict__') and |
| hasattr(func, 'func_globals') and |
| func.func_globals is module.__dict__): |
| return module.__name__ |
| return None |
| |
| #//////////////////////////////////////////////////////////// |
| # Introspection Dispatch Table |
| #//////////////////////////////////////////////////////////// |
| |
| _introspecter_registry = [] |
| def register_introspecter(applicability_test, introspecter, priority=10): |
| """ |
| Register an introspecter function. Introspecter functions take |
| two arguments, a python value and a C{ValueDoc} object, and should |
| add information about the given value to the the C{ValueDoc}. |
| Usually, the first line of an inspecter function will specialize |
| it to a sublass of C{ValueDoc}, using L{ValueDoc.specialize_to()}: |
| |
| >>> def typical_introspecter(value, value_doc): |
| ... value_doc.specialize_to(SomeSubclassOfValueDoc) |
| ... <add info to value_doc> |
| |
| @param priority: The priority of this introspecter, which determines |
| the order in which introspecters are tried -- introspecters with lower |
| numbers are tried first. The standard introspecters have priorities |
| ranging from 20 to 30. The default priority (10) will place new |
| introspecters before standard introspecters. |
| """ |
| _introspecter_registry.append( (priority, applicability_test, |
| introspecter) ) |
| _introspecter_registry.sort() |
| |
| def _get_introspecter(value): |
| for (priority, applicability_test, introspecter) in _introspecter_registry: |
| if applicability_test(value): |
| return introspecter |
| else: |
| return introspect_other |
| |
| # Register the standard introspecter functions. |
| def is_classmethod(v): return isinstance(v, classmethod) |
| def is_staticmethod(v): return isinstance(v, staticmethod) |
| def is_property(v): return isinstance(v, property) |
| register_introspecter(inspect.ismodule, introspect_module, priority=20) |
| register_introspecter(isclass, introspect_class, priority=24) |
| register_introspecter(inspect.isroutine, introspect_routine, priority=28) |
| register_introspecter(is_property, introspect_property, priority=30) |
| |
| # Register getset_descriptor as a property |
| try: |
| import array |
| getset_type = type(array.array.typecode) |
| del array |
| def is_getset(v): return isinstance(v, getset_type) |
| register_introspecter(is_getset, introspect_property, priority=32) |
| except: |
| pass |
| |
| # Register member_descriptor as a property |
| try: |
| import datetime |
| member_type = type(datetime.timedelta.days) |
| del datetime |
| def is_member(v): return isinstance(v, member_type) |
| register_introspecter(is_member, introspect_property, priority=34) |
| except: |
| pass |
| |
| #//////////////////////////////////////////////////////////// |
| # Import support |
| #//////////////////////////////////////////////////////////// |
| |
| def get_value_from_filename(filename, context=None): |
| # Normalize the filename. |
| filename = os.path.normpath(os.path.abspath(filename)) |
| |
| # Divide the filename into a base directory and a name. (For |
| # packages, use the package's parent directory as the base, and |
| # the directory name as its name). |
| basedir = os.path.split(filename)[0] |
| name = os.path.splitext(os.path.split(filename)[1])[0] |
| if name == '__init__': |
| basedir, name = os.path.split(basedir) |
| name = DottedName(name) |
| |
| # If the context wasn't provided, then check if the file is in a |
| # package directory. If so, then update basedir & name to contain |
| # the topmost package's directory and the fully qualified name for |
| # this file. (This update assume the default value of __path__ |
| # for the parent packages; if the parent packages override their |
| # __path__s, then this can cause us not to find the value.) |
| if context is None: |
| while is_package_dir(basedir): |
| basedir, pkg_name = os.path.split(basedir) |
| name = DottedName(pkg_name, name) |
| |
| # If a parent package was specified, then find the directory of |
| # the topmost package, and the fully qualified name for this file. |
| if context is not None: |
| # Combine the name. |
| name = DottedName(context.canonical_name, name) |
| # Find the directory of the base package. |
| while context not in (None, UNKNOWN): |
| pkg_dir = os.path.split(context.filename)[0] |
| basedir = os.path.split(pkg_dir)[0] |
| context = context.package |
| |
| # Import the module. (basedir is the directory of the module's |
| # topmost package, or its own directory if it's not in a package; |
| # and name is the fully qualified dotted name for the module.) |
| old_sys_path = sys.path[:] |
| try: |
| sys.path.insert(0, basedir) |
| # This will make sure that we get the module itself, even |
| # if it is shadowed by a variable. (E.g., curses.wrapper): |
| _import(str(name)) |
| if str(name) in sys.modules: |
| return sys.modules[str(name)] |
| else: |
| # Use this as a fallback -- it *shouldn't* ever be needed. |
| return get_value_from_name(name) |
| finally: |
| sys.path = old_sys_path |
| |
| def get_value_from_scriptname(filename): |
| name = munge_script_name(filename) |
| return _import(name, filename) |
| |
| def get_value_from_name(name, globs=None): |
| """ |
| Given a name, return the corresponding value. |
| |
| @param globs: A namespace to check for the value, if there is no |
| module containing the named value. Defaults to __builtin__. |
| """ |
| name = DottedName(name) |
| |
| # Import the topmost module/package. If we fail, then check if |
| # the requested name refers to a builtin. |
| try: |
| module = _import(name[0]) |
| except ImportError, e: |
| if globs is None: globs = __builtin__.__dict__ |
| if name[0] in globs: |
| try: return _lookup(globs[name[0]], name[1:]) |
| except: raise e |
| else: |
| raise |
| |
| # Find the requested value in the module/package or its submodules. |
| for i in range(1, len(name)): |
| try: return _lookup(module, name[i:]) |
| except ImportError: pass |
| module = _import('.'.join(name[:i+1])) |
| module = _lookup(module, name[1:i+1]) |
| return module |
| |
| def _lookup(module, name): |
| val = module |
| for i, identifier in enumerate(name): |
| try: val = getattr(val, identifier) |
| except AttributeError: |
| exc_msg = ('no variable named %s in %s' % |
| (identifier, '.'.join(name[:1+i]))) |
| raise ImportError(exc_msg) |
| return val |
| |
| def _import(name, filename=None): |
| """ |
| Run the given callable in a 'sandboxed' environment. |
| Currently, this includes saving and restoring the contents of |
| sys and __builtins__; and suppressing stdin, stdout, and stderr. |
| """ |
| # Note that we just do a shallow copy of sys. In particular, |
| # any changes made to sys.modules will be kept. But we do |
| # explicitly store sys.path. |
| old_sys = sys.__dict__.copy() |
| old_sys_path = sys.path[:] |
| old_builtins = __builtin__.__dict__.copy() |
| |
| # Add the current directory to sys.path, in case they're trying to |
| # import a module by name that resides in the current directory. |
| # But add it to the end -- otherwise, the explicit directory added |
| # in get_value_from_filename might get overwritten |
| sys.path.append('') |
| |
| # Suppress input and output. (These get restored when we restore |
| # sys to old_sys). |
| sys.stdin = sys.stdout = sys.stderr = _dev_null |
| sys.__stdin__ = sys.__stdout__ = sys.__stderr__ = _dev_null |
| |
| # Remove any command-line arguments |
| sys.argv = ['(imported)'] |
| |
| try: |
| try: |
| if filename is None: |
| return __import__(name) |
| else: |
| # For importing scripts: |
| return imp.load_source(name, filename) |
| except KeyboardInterrupt: raise |
| except: |
| exc_typ, exc_val, exc_tb = sys.exc_info() |
| if exc_val is None: |
| estr = '%s' % (exc_typ,) |
| else: |
| estr = '%s: %s' % (exc_typ.__name__, exc_val) |
| if exc_tb.tb_next is not None: |
| estr += ' (line %d)' % (exc_tb.tb_next.tb_lineno,) |
| raise ImportError(estr) |
| finally: |
| # Restore the important values that we saved. |
| __builtin__.__dict__.clear() |
| __builtin__.__dict__.update(old_builtins) |
| sys.__dict__.clear() |
| sys.__dict__.update(old_sys) |
| sys.path = old_sys_path |
| |
| def introspect_docstring_lineno(api_doc): |
| """ |
| Try to determine the line number on which the given item's |
| docstring begins. Return the line number, or C{None} if the line |
| number can't be determined. The line number of the first line in |
| the file is 1. |
| """ |
| if api_doc.docstring_lineno is not UNKNOWN: |
| return api_doc.docstring_lineno |
| if isinstance(api_doc, ValueDoc) and api_doc.pyval is not UNKNOWN: |
| try: |
| lines, lineno = inspect.findsource(api_doc.pyval) |
| if not isinstance(api_doc, ModuleDoc): lineno += 1 |
| for lineno in range(lineno, len(lines)): |
| if lines[lineno].split('#', 1)[0].strip(): |
| api_doc.docstring_lineno = lineno + 1 |
| return lineno + 1 |
| except IOError: pass |
| except TypeError: pass |
| except IndexError: |
| log.warning('inspect.findsource(%s) raised IndexError' |
| % api_doc.canonical_name) |
| return None |
| |
| class _DevNull: |
| """ |
| A "file-like" object that discards anything that is written and |
| always reports end-of-file when read. C{_DevNull} is used by |
| L{_import()} to discard output when importing modules; and to |
| ensure that stdin appears closed. |
| """ |
| def __init__(self): |
| self.closed = 1 |
| self.mode = 'r+' |
| self.softspace = 0 |
| self.name='</dev/null>' |
| def close(self): pass |
| def flush(self): pass |
| def read(self, size=0): return '' |
| def readline(self, size=0): return '' |
| def readlines(self, sizehint=0): return [] |
| def seek(self, offset, whence=0): pass |
| def tell(self): return 0L |
| def truncate(self, size=0): pass |
| def write(self, str): pass |
| def writelines(self, sequence): pass |
| xreadlines = readlines |
| _dev_null = _DevNull() |
| |
| ###################################################################### |
| ## Zope InterfaceClass |
| ###################################################################### |
| |
| try: |
| from zope.interface.interface import InterfaceClass as _ZopeInterfaceClass |
| register_class_type(_ZopeInterfaceClass) |
| except: |
| pass |
| |
| ###################################################################### |
| ## Zope Extension classes |
| ###################################################################### |
| |
| try: |
| # Register type(ExtensionClass.ExtensionClass) |
| from ExtensionClass import ExtensionClass as _ExtensionClass |
| _ZopeType = type(_ExtensionClass) |
| def _is_zope_type(val): |
| return isinstance(val, _ZopeType) |
| register_introspecter(_is_zope_type, introspect_class) |
| |
| # Register ExtensionClass.*MethodType |
| from ExtensionClass import PythonMethodType as _ZopeMethodType |
| from ExtensionClass import ExtensionMethodType as _ZopeCMethodType |
| def _is_zope_method(val): |
| return isinstance(val, (_ZopeMethodType, _ZopeCMethodType)) |
| register_introspecter(_is_zope_method, introspect_routine) |
| except: |
| pass |
| |
| |
| |
| |
| # [xx] |
| 0 # hm.. otherwise the following gets treated as a docstring! ouch! |
| """ |
| ###################################################################### |
| ## Zope Extension... |
| ###################################################################### |
| class ZopeIntrospecter(Introspecter): |
| VALUEDOC_CLASSES = Introspecter.VALUEDOC_CLASSES.copy() |
| VALUEDOC_CLASSES.update({ |
| 'module': ZopeModuleDoc, |
| 'class': ZopeClassDoc, |
| 'interface': ZopeInterfaceDoc, |
| 'attribute': ZopeAttributeDoc, |
| }) |
| |
| def add_module_child(self, child, child_name, module_doc): |
| if isinstance(child, zope.interfaces.Interface): |
| module_doc.add_zope_interface(child_name) |
| else: |
| Introspecter.add_module_child(self, child, child_name, module_doc) |
| |
| def add_class_child(self, child, child_name, class_doc): |
| if isinstance(child, zope.interfaces.Interface): |
| class_doc.add_zope_interface(child_name) |
| else: |
| Introspecter.add_class_child(self, child, child_name, class_doc) |
| |
| def introspect_zope_interface(self, interface, interfacename): |
| pass # etc... |
| """ |