Source code for pyquickhelper.sphinxext.import_object_helper

# -*- coding: utf-8 -*-
"""
Defines a :epkg:`sphinx` extension which if all parameters are documented.


:githublink:`%|py|6`
"""
import inspect
from typing import Tuple
import warnings
import sys


[docs]class _Types: @property def prop(self): pass @staticmethod def stat(): pass
[docs]def import_object(docname, kind, use_init=True, fLOG=None) -> Tuple[object, str]: """ Extracts an object defined by its name including the module name. :param docname: full name of the object (example: ``pyquickhelper.sphinxext.sphinx_docassert_extension.import_object``) :param kind: ``'function'`` or ``'class'`` or ``'kind'`` :param use_init: return the constructor instead of the class :param fLOG: logging function :return: tuple(object, name) @raises :epkg:`*py:RuntimeError` if cannot be imported, :epkg:`*py:TypeError` if it is a method or a property, :epkg:`*py:ValueError` if *kind* is unknown. :githublink:`%|py|35` """ spl = docname.split(".") name = spl[-1] if kind not in ("method", "property", "staticmethod"): modname = ".".join(spl[:-1]) code = 'from {0} import {1}\nmyfunc = {1}'.format(modname, name) codeobj = compile(code, 'conf{0}.py'.format(kind), 'exec') if fLOG: fLOG("[import_object] modname='{0}' code='{1}'".format( modname, code)) else: modname = ".".join(spl[:-2]) classname = spl[-2] code = 'from {0} import {1}\nmyfunc = {1}'.format(modname, classname) codeobj = compile(code, 'conf{0}2.py'.format(kind), 'exec') if fLOG: fLOG("[import_object] modname='{0}' code='{1}' classname='{2}'".format( modname, code, classname)) context = {} with warnings.catch_warnings(): warnings.simplefilter("ignore") try: exec(codeobj, context, context) except Exception as e: mes = "Unable to compile and execute '{0}' due to \n{1}\ngiven:\n{2}".format( code.replace('\n', '\\n'), e, docname) if fLOG: fLOG("[import_object] failed due to {0}".format(e)) raise RuntimeError(mes) from e myfunc = context["myfunc"] if fLOG: fLOG( "[import_object] imported '{0}' --> '{1}'".format(docname, str(myfunc))) if kind == "function": if not inspect.isfunction(myfunc) and 'built-in function' not in str(myfunc) and \ 'built-in method' not in str(myfunc): # inspect.isfunction fails for C functions. raise TypeError("'{0}' is not a function".format(docname)) name = spl[-1] elif kind == "property": if not inspect.isclass(myfunc): raise TypeError("'{0}' is not a class".format(docname)) myfunc = getattr(myfunc, spl[-1]) if inspect.isfunction(myfunc) or inspect.ismethod(myfunc): raise TypeError( "'{0}' is not a property - {1}".format(docname, myfunc)) if (hasattr(_Types.prop, '__class__') and myfunc.__class__ is not _Types.prop.__class__): # pylint: disable=E1101 raise TypeError( "'{0}' is not a property(*) - {1}".format(docname, myfunc)) if not isinstance(myfunc, property): raise TypeError( "'{0}' is not a static property(**) - {1}".format(docname, myfunc)) name = spl[-1] elif kind == "method": if not inspect.isclass(myfunc): raise TypeError("'{0}' is not a class".format(docname)) myfunc = getattr(myfunc, spl[-1]) if not inspect.isfunction(myfunc) and not inspect.ismethod(myfunc) and not name.endswith('__'): raise TypeError( "'{0}' is not a method - {1}".format(docname, myfunc)) if isinstance(myfunc, staticmethod): raise TypeError( "'{0}' is not a method(*) - {1}".format(docname, myfunc)) if hasattr(myfunc, "__code__") and sys.version_info >= (3, 4): if len(myfunc.__code__.co_varnames) == 0: raise TypeError( "'{0}' is not a method(**) - {1}".format(docname, myfunc)) if myfunc.__code__.co_varnames[0] != 'self': raise TypeError( "'{0}' is not a method(***) - {1}".format(docname, myfunc)) name = spl[-1] elif kind == "staticmethod": if not inspect.isclass(myfunc): raise TypeError("'{0}' is not a class".format(docname)) myfunc = getattr(myfunc, spl[-1]) if not inspect.isfunction(myfunc) and not inspect.ismethod(myfunc): raise TypeError( "'{0}' is not a static method - {1}".format(docname, myfunc)) if myfunc.__class__ is not _Types.stat.__class__: raise TypeError( "'{0}' is not a static method(*) - {1}".format(docname, myfunc)) name = spl[-1] elif kind == "class": if not inspect.isclass(myfunc): raise TypeError("'{0}' is not a class".format(docname)) name = spl[-1] myfunc = myfunc.__init__ if use_init else myfunc else: raise ValueError("Unknwon value for 'kind'") return myfunc, name
[docs]def import_any_object(docname, use_init=True, fLOG=None) -> Tuple[object, str, str]: """ Extracts an object defined by its name including the module name. :param docname: full name of the object (example: ``pyquickhelper.sphinxext.sphinx_docassert_extension.import_object``) :param use_init: return the constructor instead of the class :param fLOG: logging function :returns: tuple(object, name, kind) :raises: :epkg:`*py:ImportError` if unable to import Kind is among ``'function'`` or ``'class'`` or ``'kind'``. :githublink:`%|py|143` """ myfunc = None name = None excs = [] for kind in ("function", "method", "staticmethod", "property", "class"): try: myfunc, name = import_object( docname, kind, use_init=use_init, fLOG=fLOG) if fLOG: fLOG( "[import_any_object] ok '{0}' for '{1}' - use_unit={2}".format(kind, docname, use_init)) fLOG("[import_any_object] __doc__={0} __name__={1} __module__={2}".format( hasattr(myfunc, '__doc__'), hasattr(myfunc, '__name__'), hasattr(myfunc, '__module__'))) fLOG("[import_any_object] name='{0}' - module='{1}'".format( name, getattr(myfunc, '__module__', None))) return myfunc, name, kind except Exception as e: # not this kind excs.append((kind, e)) if fLOG: fLOG( "[import_any_object] not '{0}' for '{1}' (use_unit={2})".format(kind, docname, use_init)) sec = " ### ".join("{0}-{1}-{2}".format(k, type(e), e).replace("\n", " ") for k, e in excs) raise ImportError( "Unable to import '{0}'. Exceptions met: {1}".format(docname, sec))
[docs]def import_path(obj, class_name=None, err_msg=None, fLOG=None): """ Determines the import path which is the shortest way to import the function. In case the following ``from module.submodule import function`` works, the import path will be ``module.submodule``. :param obj: object :param class_name: :epkg:`Python` does not really distinguish between static method and functions. If not None, this parameter should contain the name of the class which holds the static method given in *obj* :param err_msg: an error message to display if anything happens :param fLOG: logging function :returns: import path :raises: :epkg:`*py:TypeError` if object is a property, :epkg:`*py:RuntimeError` if cannot be imported The function does not work for methods or properties. It raises an exception or returns irrelevant results. .. versionchanged:: 1.8 Parameter *err_msg* was added. :githublink:`%|py|196` """ try: _ = obj.__module__ except AttributeError: # This is a method. raise TypeError("obj is a method or a property ({0})".format(obj)) if class_name is None: name = obj.__name__ else: name = class_name elements = obj.__module__.split('.') found = None for i in range(1, len(elements) + 1): path = '.'.join(elements[:i]) code = 'from {0} import {1}'.format(path, name) codeobj = compile(code, 'import_path_{0}.py'.format(name), 'exec') with warnings.catch_warnings(): warnings.simplefilter("ignore") context = {} try: exec(codeobj, context, context) found = path if fLOG: fLOG("[import_path] succeeds: '{0}'".format(code)) break except Exception: if fLOG: fLOG("[import_path] fails: '{0}'".format(code)) continue if found is None: raise RuntimeError("Unable to import object '{0}' ({1}). Full path: '{2}'{3}".format( name, obj, '.'.join(elements), ("\n-----\n" + err_msg) if err_msg else '')) return found