doccer.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366
  1. """Utilities to allow inserting docstring fragments for common
  2. parameters into function and method docstrings."""
  3. from collections.abc import Callable, Iterable, Mapping
  4. from typing import Protocol, TypeVar
  5. import sys
  6. __all__ = [
  7. "docformat",
  8. "inherit_docstring_from",
  9. "indentcount_lines",
  10. "filldoc",
  11. "unindent_dict",
  12. "unindent_string",
  13. "extend_notes_in_docstring",
  14. "replace_notes_in_docstring",
  15. "doc_replace",
  16. ]
  17. _F = TypeVar("_F", bound=Callable[..., object])
  18. class Decorator(Protocol):
  19. """A decorator of a function."""
  20. def __call__(self, func: _F, /) -> _F: ...
  21. def docformat(docstring: str, docdict: Mapping[str, str] | None = None) -> str:
  22. """Fill a function docstring from variables in dictionary.
  23. Adapt the indent of the inserted docs
  24. Parameters
  25. ----------
  26. docstring : str
  27. A docstring from a function, possibly with dict formatting strings.
  28. docdict : dict[str, str], optional
  29. A dictionary with keys that match the dict formatting strings
  30. and values that are docstring fragments to be inserted. The
  31. indentation of the inserted docstrings is set to match the
  32. minimum indentation of the ``docstring`` by adding this
  33. indentation to all lines of the inserted string, except the
  34. first.
  35. Returns
  36. -------
  37. docstring : str
  38. string with requested ``docdict`` strings inserted.
  39. Examples
  40. --------
  41. >>> docformat(' Test string with %(value)s', {'value':'inserted value'})
  42. ' Test string with inserted value'
  43. >>> docstring = 'First line\\n Second line\\n %(value)s'
  44. >>> inserted_string = "indented\\nstring"
  45. >>> docdict = {'value': inserted_string}
  46. >>> docformat(docstring, docdict)
  47. 'First line\\n Second line\\n indented\\n string'
  48. """
  49. if not docstring:
  50. return docstring
  51. if docdict is None:
  52. docdict = {}
  53. if not docdict:
  54. return docstring
  55. lines = docstring.expandtabs().splitlines()
  56. # Find the minimum indent of the main docstring, after first line
  57. if len(lines) < 2:
  58. icount = 0
  59. else:
  60. icount = indentcount_lines(lines[1:])
  61. indent = " " * icount
  62. # Insert this indent to dictionary docstrings
  63. indented = {}
  64. for name, dstr in docdict.items():
  65. lines = dstr.expandtabs().splitlines()
  66. indented[name] = ("\n" + indent).join(lines)
  67. return docstring % indented
  68. def inherit_docstring_from(cls: object) -> Decorator:
  69. """This decorator modifies the decorated function's docstring by
  70. replacing occurrences of '%(super)s' with the docstring of the
  71. method of the same name from the class `cls`.
  72. If the decorated method has no docstring, it is simply given the
  73. docstring of `cls`s method.
  74. Parameters
  75. ----------
  76. cls : type or object
  77. A class with a method with the same name as the decorated method.
  78. The docstring of the method in this class replaces '%(super)s' in the
  79. docstring of the decorated method.
  80. Returns
  81. -------
  82. decfunc : function
  83. The decorator function that modifies the __doc__ attribute
  84. of its argument.
  85. Examples
  86. --------
  87. In the following, the docstring for Bar.func created using the
  88. docstring of `Foo.func`.
  89. >>> class Foo:
  90. ... def func(self):
  91. ... '''Do something useful.'''
  92. ... return
  93. ...
  94. >>> class Bar(Foo):
  95. ... @inherit_docstring_from(Foo)
  96. ... def func(self):
  97. ... '''%(super)s
  98. ... Do it fast.
  99. ... '''
  100. ... return
  101. ...
  102. >>> b = Bar()
  103. >>> b.func.__doc__
  104. 'Do something useful.\n Do it fast.\n '
  105. """
  106. def _doc(func: _F) -> _F:
  107. cls_docstring = getattr(cls, func.__name__).__doc__
  108. func_docstring = func.__doc__
  109. if func_docstring is None:
  110. func.__doc__ = cls_docstring
  111. else:
  112. new_docstring = func_docstring % dict(super=cls_docstring)
  113. func.__doc__ = new_docstring
  114. return func
  115. return _doc
  116. def extend_notes_in_docstring(cls: object, notes: str) -> Decorator:
  117. """This decorator replaces the decorated function's docstring
  118. with the docstring from corresponding method in `cls`.
  119. It extends the 'Notes' section of that docstring to include
  120. the given `notes`.
  121. Parameters
  122. ----------
  123. cls : type or object
  124. A class with a method with the same name as the decorated method.
  125. The docstring of the method in this class replaces the docstring of the
  126. decorated method.
  127. notes : str
  128. Additional notes to append to the 'Notes' section of the docstring.
  129. Returns
  130. -------
  131. decfunc : function
  132. The decorator function that modifies the __doc__ attribute
  133. of its argument.
  134. """
  135. def _doc(func: _F) -> _F:
  136. cls_docstring = getattr(cls, func.__name__).__doc__
  137. # If python is called with -OO option,
  138. # there is no docstring
  139. if cls_docstring is None:
  140. return func
  141. end_of_notes = cls_docstring.find(" References\n")
  142. if end_of_notes == -1:
  143. end_of_notes = cls_docstring.find(" Examples\n")
  144. if end_of_notes == -1:
  145. end_of_notes = len(cls_docstring)
  146. func.__doc__ = (
  147. cls_docstring[:end_of_notes] + notes + cls_docstring[end_of_notes:]
  148. )
  149. return func
  150. return _doc
  151. def replace_notes_in_docstring(cls: object, notes: str) -> Decorator:
  152. """This decorator replaces the decorated function's docstring
  153. with the docstring from corresponding method in `cls`.
  154. It replaces the 'Notes' section of that docstring with
  155. the given `notes`.
  156. Parameters
  157. ----------
  158. cls : type or object
  159. A class with a method with the same name as the decorated method.
  160. The docstring of the method in this class replaces the docstring of the
  161. decorated method.
  162. notes : str
  163. The notes to replace the existing 'Notes' section with.
  164. Returns
  165. -------
  166. decfunc : function
  167. The decorator function that modifies the __doc__ attribute
  168. of its argument.
  169. """
  170. def _doc(func: _F) -> _F:
  171. cls_docstring = getattr(cls, func.__name__).__doc__
  172. notes_header = " Notes\n -----\n"
  173. # If python is called with -OO option,
  174. # there is no docstring
  175. if cls_docstring is None:
  176. return func
  177. start_of_notes = cls_docstring.find(notes_header)
  178. end_of_notes = cls_docstring.find(" References\n")
  179. if end_of_notes == -1:
  180. end_of_notes = cls_docstring.find(" Examples\n")
  181. if end_of_notes == -1:
  182. end_of_notes = len(cls_docstring)
  183. func.__doc__ = (
  184. cls_docstring[: start_of_notes + len(notes_header)]
  185. + notes
  186. + cls_docstring[end_of_notes:]
  187. )
  188. return func
  189. return _doc
  190. def indentcount_lines(lines: Iterable[str]) -> int:
  191. """Minimum indent for all lines in line list
  192. Parameters
  193. ----------
  194. lines : Iterable[str]
  195. The lines to find the minimum indent of.
  196. Returns
  197. -------
  198. indent : int
  199. The minimum indent.
  200. Examples
  201. --------
  202. >>> lines = [' one', ' two', ' three']
  203. >>> indentcount_lines(lines)
  204. 1
  205. >>> lines = []
  206. >>> indentcount_lines(lines)
  207. 0
  208. >>> lines = [' one']
  209. >>> indentcount_lines(lines)
  210. 1
  211. >>> indentcount_lines([' '])
  212. 0
  213. """
  214. indentno = sys.maxsize
  215. for line in lines:
  216. stripped = line.lstrip()
  217. if stripped:
  218. indentno = min(indentno, len(line) - len(stripped))
  219. if indentno == sys.maxsize:
  220. return 0
  221. return indentno
  222. def filldoc(docdict: Mapping[str, str], unindent_params: bool = True) -> Decorator:
  223. """Return docstring decorator using docdict variable dictionary.
  224. Parameters
  225. ----------
  226. docdict : dict[str, str]
  227. A dictionary containing name, docstring fragment pairs.
  228. unindent_params : bool, optional
  229. If True, strip common indentation from all parameters in docdict.
  230. Default is False.
  231. Returns
  232. -------
  233. decfunc : function
  234. The decorator function that applies dictionary to its
  235. argument's __doc__ attribute.
  236. """
  237. if unindent_params:
  238. docdict = unindent_dict(docdict)
  239. def decorate(func: _F) -> _F:
  240. # __doc__ may be None for optimized Python (-OO)
  241. doc = func.__doc__ or ""
  242. func.__doc__ = docformat(doc, docdict)
  243. return func
  244. return decorate
  245. def unindent_dict(docdict: Mapping[str, str]) -> dict[str, str]:
  246. """Unindent all strings in a docdict.
  247. Parameters
  248. ----------
  249. docdict : dict[str, str]
  250. A dictionary with string values to unindent.
  251. Returns
  252. -------
  253. docdict : dict[str, str]
  254. The `docdict` dictionary but each of its string values are unindented.
  255. """
  256. can_dict: dict[str, str] = {}
  257. for name, dstr in docdict.items():
  258. can_dict[name] = unindent_string(dstr)
  259. return can_dict
  260. def unindent_string(docstring: str) -> str:
  261. """Set docstring to minimum indent for all lines, including first.
  262. Parameters
  263. ----------
  264. docstring : str
  265. The input docstring to unindent.
  266. Returns
  267. -------
  268. docstring : str
  269. The unindented docstring.
  270. Examples
  271. --------
  272. >>> unindent_string(' two')
  273. 'two'
  274. >>> unindent_string(' two\\n three')
  275. 'two\\n three'
  276. """
  277. lines = docstring.expandtabs().splitlines()
  278. icount = indentcount_lines(lines)
  279. if icount == 0:
  280. return docstring
  281. return "\n".join([line[icount:] for line in lines])
  282. def doc_replace(obj: object, oldval: str, newval: str) -> Decorator:
  283. """Decorator to take the docstring from obj, with oldval replaced by newval
  284. Equivalent to ``func.__doc__ = obj.__doc__.replace(oldval, newval)``
  285. Parameters
  286. ----------
  287. obj : object
  288. A class or object whose docstring will be used as the basis for the
  289. replacement operation.
  290. oldval : str
  291. The string to search for in the docstring.
  292. newval : str
  293. The string to replace `oldval` with in the docstring.
  294. Returns
  295. -------
  296. decfunc : function
  297. A decorator function that replaces occurrences of `oldval` with `newval`
  298. in the docstring of the decorated function.
  299. """
  300. # __doc__ may be None for optimized Python (-OO)
  301. doc = (obj.__doc__ or "").replace(oldval, newval)
  302. def inner(func: _F) -> _F:
  303. func.__doc__ = doc
  304. return func
  305. return inner