Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# -*- coding: utf-8 -*-
2"""
3@file
4@brief Defines a :epkg:`sphinx` extension which if all parameters are documented.
5"""
6import inspect
7from typing import Tuple
8import warnings
9import sys
12class _Types:
13 @property
14 def prop(self):
15 pass
17 @staticmethod
18 def stat():
19 pass
22def import_object(docname, kind, use_init=True, fLOG=None) -> Tuple[object, str]:
23 """
24 Extracts an object defined by its name including the module name.
26 @param docname full name of the object
27 (example: ``pyquickhelper.sphinxext.sphinx_docassert_extension.import_object``)
28 @param kind ``'function'`` or ``'class'`` or ``'kind'``
29 @param use_init return the constructor instead of the class
30 @param fLOG logging function
31 @return tuple(object, name)
32 @raises :epkg:`*py:RuntimeError` if cannot be imported,
33 :epkg:`*py:TypeError` if it is a method or a property,
34 :epkg:`*py:ValueError` if *kind* is unknown.
35 """
36 spl = docname.split(".")
37 name = spl[-1]
38 if kind not in ("method", "property", "staticmethod"):
39 modname = ".".join(spl[:-1])
40 code = 'from {0} import {1}\nmyfunc = {1}'.format(modname, name)
41 codeobj = compile(code, 'conf{0}.py'.format(kind), 'exec')
42 if fLOG:
43 fLOG("[import_object] modname='{0}' code='{1}'".format(
44 modname, code))
45 else:
46 modname = ".".join(spl[:-2])
47 classname = spl[-2]
48 code = 'from {0} import {1}\nmyfunc = {1}'.format(modname, classname)
49 codeobj = compile(code, 'conf{0}2.py'.format(kind), 'exec')
50 if fLOG:
51 fLOG("[import_object] modname='{0}' code='{1}' classname='{2}'".format(
52 modname, code, classname))
54 context = {}
55 with warnings.catch_warnings():
56 warnings.simplefilter("ignore")
57 try:
58 exec(codeobj, context, context)
59 except Exception as e:
60 mes = "Unable to compile and execute '{0}' due to \n{1}\ngiven:\n{2}".format(
61 code.replace('\n', '\\n'), e, docname)
62 if fLOG:
63 fLOG("[import_object] failed due to {0}".format(e))
64 raise RuntimeError(mes) from e
66 myfunc = context["myfunc"]
67 if fLOG:
68 fLOG(
69 "[import_object] imported '{0}' --> '{1}'".format(docname, str(myfunc)))
70 if kind == "function":
71 if not inspect.isfunction(myfunc) and 'built-in function' not in str(myfunc) and \
72 'built-in method' not in str(myfunc):
73 # inspect.isfunction fails for C functions.
74 raise TypeError("'{0}' is not a function".format(docname))
75 name = spl[-1]
76 elif kind == "property":
77 if not inspect.isclass(myfunc):
78 raise TypeError("'{0}' is not a class".format(docname))
79 myfunc = getattr(myfunc, spl[-1])
80 if inspect.isfunction(myfunc) or inspect.ismethod(myfunc):
81 raise TypeError(
82 "'{0}' is not a property - {1}".format(docname, myfunc))
83 if (hasattr(_Types.prop, '__class__') and
84 myfunc.__class__ is not _Types.prop.__class__): # pylint: disable=E1101
85 raise TypeError(
86 "'{0}' is not a property(*) - {1}".format(docname, myfunc))
87 if not isinstance(myfunc, property):
88 raise TypeError(
89 "'{0}' is not a static property(**) - {1}".format(docname, myfunc))
90 name = spl[-1]
91 elif kind == "method":
92 if not inspect.isclass(myfunc):
93 raise TypeError("'{0}' is not a class".format(docname))
94 myfunc = getattr(myfunc, spl[-1])
95 if not inspect.isfunction(myfunc) and not inspect.ismethod(myfunc) and not name.endswith('__'):
96 raise TypeError(
97 "'{0}' is not a method - {1}".format(docname, myfunc))
98 if isinstance(myfunc, staticmethod):
99 raise TypeError(
100 "'{0}' is not a method(*) - {1}".format(docname, myfunc))
101 if hasattr(myfunc, "__code__") and sys.version_info >= (3, 4):
102 if len(myfunc.__code__.co_varnames) == 0:
103 raise TypeError(
104 "'{0}' is not a method(**) - {1}".format(docname, myfunc))
105 if myfunc.__code__.co_varnames[0] != 'self':
106 raise TypeError(
107 "'{0}' is not a method(***) - {1}".format(docname, myfunc))
108 name = spl[-1]
109 elif kind == "staticmethod":
110 if not inspect.isclass(myfunc):
111 raise TypeError("'{0}' is not a class".format(docname))
112 myfunc = getattr(myfunc, spl[-1])
113 if not inspect.isfunction(myfunc) and not inspect.ismethod(myfunc):
114 raise TypeError(
115 "'{0}' is not a static method - {1}".format(docname, myfunc))
116 if myfunc.__class__ is not _Types.stat.__class__:
117 raise TypeError(
118 "'{0}' is not a static method(*) - {1}".format(docname, myfunc))
119 name = spl[-1]
120 elif kind == "class":
121 if not inspect.isclass(myfunc):
122 raise TypeError("'{0}' is not a class".format(docname))
123 name = spl[-1]
124 myfunc = myfunc.__init__ if use_init else myfunc
125 else:
126 raise ValueError("Unknwon value for 'kind'")
128 return myfunc, name
131def import_any_object(docname, use_init=True, fLOG=None) -> Tuple[object, str, str]:
132 """
133 Extracts an object defined by its name including the module name.
135 :param docname: full name of the object
136 (example: ``pyquickhelper.sphinxext.sphinx_docassert_extension.import_object``)
137 :param use_init: return the constructor instead of the class
138 :param fLOG: logging function
139 :returns: tuple(object, name, kind)
140 :raises: :epkg:`*py:ImportError` if unable to import
142 Kind is among ``'function'`` or ``'class'`` or ``'kind'``.
143 """
144 myfunc = None
145 name = None
146 excs = []
147 for kind in ("function", "method", "staticmethod", "property", "class"):
148 try:
149 myfunc, name = import_object(
150 docname, kind, use_init=use_init, fLOG=fLOG)
151 if fLOG:
152 fLOG(
153 "[import_any_object] ok '{0}' for '{1}' - use_unit={2}".format(kind, docname, use_init))
154 fLOG("[import_any_object] __doc__={0} __name__={1} __module__={2}".format(
155 hasattr(myfunc, '__doc__'), hasattr(myfunc, '__name__'),
156 hasattr(myfunc, '__module__')))
157 fLOG("[import_any_object] name='{0}' - module='{1}'".format(
158 name, getattr(myfunc, '__module__', None)))
159 return myfunc, name, kind
160 except Exception as e:
161 # not this kind
162 excs.append((kind, e))
163 if fLOG:
164 fLOG(
165 "[import_any_object] not '{0}' for '{1}' (use_unit={2})".format(kind, docname, use_init))
167 sec = " ### ".join("{0}-{1}-{2}".format(k, type(e), e).replace("\n", " ")
168 for k, e in excs)
169 raise ImportError(
170 "Unable to import '{0}'. Exceptions met: {1}".format(docname, sec))
173def import_path(obj, class_name=None, err_msg=None, fLOG=None):
174 """
175 Determines the import path which is
176 the shortest way to import the function. In case the
177 following ``from module.submodule import function``
178 works, the import path will be ``module.submodule``.
180 :param obj: object
181 :param class_name: :epkg:`Python` does not really distinguish between
182 static method and functions. If not None, this parameter
183 should contain the name of the class which holds the static
184 method given in *obj*
185 :param err_msg: an error message to display if anything happens
186 :param fLOG: logging function
187 :returns: import path
188 :raises: :epkg:`*py:TypeError` if object is a property,
189 :epkg:`*py:RuntimeError` if cannot be imported
191 The function does not work for methods or properties.
192 It raises an exception or returns irrelevant results.
193 """
194 try:
195 _ = obj.__module__
196 except AttributeError:
197 # This is a method.
198 raise TypeError("obj is a method or a property ({0})".format(obj))
200 if class_name is None:
201 name = obj.__name__
202 else:
203 name = class_name
204 elements = obj.__module__.split('.')
205 found = None
206 for i in range(1, len(elements) + 1):
207 path = '.'.join(elements[:i])
208 code = 'from {0} import {1}'.format(path, name)
209 codeobj = compile(code, 'import_path_{0}.py'.format(name), 'exec')
210 with warnings.catch_warnings():
211 warnings.simplefilter("ignore")
212 context = {}
213 try:
214 exec(codeobj, context, context)
215 found = path
216 if fLOG:
217 fLOG("[import_path] succeeds: '{0}'".format(code))
218 break
219 except Exception:
220 if fLOG:
221 fLOG("[import_path] fails: '{0}'".format(code))
222 continue
224 if found is None:
225 raise RuntimeError("Unable to import object '{0}' ({1}). Full path: '{2}'{3}".format(
226 name, obj, '.'.join(elements), ("\n-----\n" + err_msg) if err_msg else ''))
227 return found