• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

domdfcoding / domdf_python_tools / 7166137878

11 Dec 2023 10:30AM CUT coverage: 97.456%. Remained the same
7166137878

push

github

domdfcoding
Bump version v3.7.0 -> v3.8.0

1 of 1 new or added line in 1 file covered. (100.0%)

2145 of 2201 relevant lines covered (97.46%)

0.97 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

100.0
/domdf_python_tools/doctools.py
1
#  !/usr/bin/env python
2
#
3
#  doctools.py
4
"""
1✔
5
Utilities for documenting functions, classes and methods.
6

7
.. autosummary-widths:: 5/16
8

9
.. automodulesumm:: domdf_python_tools.doctools
10
        :autosummary-sections: Data
11

12
.. autosummary-widths:: 17/32
13

14
.. automodulesumm:: domdf_python_tools.doctools
15
        :autosummary-sections: Functions
16
"""
17
#
18
#  Copyright © 2020 Dominic Davis-Foster <dominic@davis-foster.co.uk>
19
#  Based on https://softwareengineering.stackexchange.com/a/386758
20
#  Copyright © amon (https://softwareengineering.stackexchange.com/users/60357/amon)
21
#  Licensed under CC BY-SA 4.0
22
#
23
#  Permission is hereby granted, free of charge, to any person obtaining a copy
24
#  of this software and associated documentation files (the "Software"), to deal
25
#  in the Software without restriction, including without limitation the rights
26
#  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
27
#  copies of the Software, and to permit persons to whom the Software is
28
#  furnished to do so, subject to the following conditions:
29
#
30
#  The above copyright notice and this permission notice shall be included in all
31
#  copies or substantial portions of the Software.
32
#
33
#  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
34
#  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
35
#  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
36
#  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
37
#  DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
38
#  OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
39
#  OR OTHER DEALINGS IN THE SOFTWARE.
40
#
41

42
# stdlib
43
import builtins
1✔
44
from contextlib import suppress
1✔
45
from inspect import cleandoc
1✔
46
from types import MethodType
1✔
47
from typing import Any, Callable, Dict, Optional, Sequence, Type, TypeVar, Union
1✔
48

49
# this package
50
from domdf_python_tools.compat import PYPY, PYPY37
1✔
51
from domdf_python_tools.typing import MethodDescriptorType, MethodWrapperType, WrapperDescriptorType
1✔
52

53
__all__ = [
1✔
54
                "_F",
55
                "_T",
56
                "deindent_string",
57
                "document_object_from_another",
58
                "append_doctring_from_another",
59
                "make_sphinx_links",
60
                "is_documented_by",
61
                "append_docstring_from",
62
                "sphinxify_docstring",
63
                "prettify_docstrings",
64
                ]
65

66
_F = TypeVar("_F", bound=Callable[..., Any])
1✔
67
_T = TypeVar("_T", bound=Type)
1✔
68

69

70
def deindent_string(string: Optional[str]) -> str:
1✔
71
        """
72
        Removes all indentation from the given string.
73

74
        :param string: The string to deindent
75

76
        :return: The string without indentation
77
        """
78

79
        if not string:
1✔
80
                # Short circuit if empty string or None
81
                return ''
1✔
82

83
        split_string = string.split('\n')
1✔
84
        deindented_string = [line.lstrip("\t ") for line in split_string]
1✔
85
        return '\n'.join(deindented_string)
1✔
86

87

88
# Functions that do the work
89
def document_object_from_another(target: Union[Type, Callable], original: Union[Type, Callable]):
1✔
90
        """
91
        Sets the docstring of the ``target`` function to that of the ``original`` function.
92

93
        This may be useful for subclasses or wrappers that use the same arguments.
94

95
        :param target: The object to set the docstring for
96
        :param original: The object to copy the docstring from
97
        """
98

99
        target.__doc__ = original.__doc__
1✔
100

101

102
def append_doctring_from_another(target: Union[Type, Callable], original: Union[Type, Callable]):
1✔
103
        """
104
        Sets the docstring of the ``target`` function to that of the ``original`` function.
105

106
        This may be useful for subclasses or wrappers that use the same arguments.
107

108
        Any indentation in either docstring is removed to
109
        ensure consistent indentation between the two docstrings.
110
        Bear this in mind if additional indentation is used in the docstring.
111

112
        :param target: The object to append the docstring to
113
        :param original: The object to copy the docstring from
114
        """
115

116
        # this package
117
        from domdf_python_tools.stringlist import StringList
1✔
118

119
        target_doc = target.__doc__
1✔
120
        original_doc = original.__doc__
1✔
121

122
        if isinstance(original_doc, str) and isinstance(target_doc, str):
1✔
123
                docstring = StringList(cleandoc(target_doc))
1✔
124
                docstring.blankline(ensure_single=True)
1✔
125
                docstring.append(cleandoc(original_doc))
1✔
126
                docstring.blankline(ensure_single=True)
1✔
127
                target.__doc__ = str(docstring)
1✔
128

129
        elif not isinstance(target_doc, str) and isinstance(original_doc, str):
1✔
130
                docstring = StringList(cleandoc(original_doc))
1✔
131
                docstring.blankline(ensure_single=True)
1✔
132
                target.__doc__ = str(docstring)
1✔
133

134

135
def make_sphinx_links(input_string: str, builtins_list: Optional[Sequence[str]] = None) -> str:
1✔
136
        r"""
137
        Make proper sphinx links out of double-backticked strings in docstring.
138

139
        i.e. :inline-code:`\`\`str\`\`` becomes  :inline-code:`:class:\`str\``
140

141
        Make sure to include the following in your ``conf.py`` file for Sphinx:
142

143
        .. code-block:: python
144

145
                intersphinx_mapping = {"python": ("https://docs.python.org/3/", None)}
146

147
        :param input_string: The string to process.
148
        :param builtins_list: A list of builtins to make links for.
149
        :default builtins_list: dir(:py:obj:`builtins`)
150

151
        :return: Processed string with links.
152
        """
153

154
        if builtins_list is None:
1✔
155
                builtins_list = dir(builtins)
1✔
156

157
        working_string = f"{input_string}"
1✔
158

159
        for builtin in builtins_list:
1✔
160
                if builtin.startswith("__"):
1✔
161
                        continue
1✔
162

163
                if builtin in {"None", "False", "None"}:
1✔
164
                        working_string = working_string.replace(f"``{builtin}``", f":py:obj:`{builtin}`")
1✔
165
                else:
166
                        working_string = working_string.replace(f"``{builtin}``", f":class:`{builtin}`")
1✔
167

168
        return working_string
1✔
169

170

171
# Decorators that call the above functions
172
def is_documented_by(original: Callable) -> Callable[[_F], _F]:
1✔
173
        """
174
        Decorator to set the docstring of the ``target`` function to that of the ``original`` function.
175

176
        This may be useful for subclasses or wrappers that use the same arguments.
177

178
        :param original:
179
        """
180

181
        def wrapper(target: _F) -> _F:
1✔
182
                document_object_from_another(target, original)
1✔
183
                return target
1✔
184

185
        return wrapper
1✔
186

187

188
def append_docstring_from(original: Callable) -> Callable[[_F], _F]:
1✔
189
        """
190
        Decorator to appends the docstring from the ``original`` function to the ``target`` function.
191

192
        This may be useful for subclasses or wrappers that use the same arguments.
193

194
        Any indentation in either docstring is removed to
195
        ensure consistent indentation between the two docstrings.
196
        Bear this in mind if additional indentation is used in the docstring.
197

198
        :param original:
199
        """
200

201
        def wrapper(target: _F) -> _F:
1✔
202
                append_doctring_from_another(target, original)
1✔
203
                return target
1✔
204

205
        return wrapper
1✔
206

207

208
def sphinxify_docstring() -> Callable[[_F], _F]:
1✔
209
        r"""
210
        Decorator to make proper sphinx links out of double-backticked strings in the docstring.
211

212
        i.e. :inline-code:`\`\`str\`\`` becomes  :inline-code:`:class:\`str\``
213

214
        Make sure to include the following in your ``conf.py`` file for Sphinx:
215

216
        .. code-block:: python
217

218
                intersphinx_mapping = {
219
                        "python": ("https://docs.python.org/3/", None),
220
                }
221
        """
222

223
        def wrapper(target: _F) -> _F:
1✔
224
                target_doc = target.__doc__
1✔
225

226
                if target_doc:
1✔
227
                        target.__doc__ = make_sphinx_links(target_doc)
1✔
228

229
                return target
1✔
230

231
        return wrapper
1✔
232

233

234
# Check against object
235
base_new_docstrings = {
1✔
236
                "__delattr__": "Implement :func:`delattr(self, name) <delattr>`.",
237
                "__dir__": "Default :func:`dir` implementation.",
238
                "__eq__": "Return ``self == other``.",  # __format__
239
                "__getattribute__": "Return :func:`getattr(self, name) <getattr>`.",
240
                "__ge__": "Return ``self >= other``.",
241
                "__gt__": "Return ``self > other``.",
242
                "__hash__": "Return :func:`hash(self) <hash>`.",
243
                # __init_subclass__
244
                # __init__  # not usually shown in sphinx
245
                "__lt__": "Return ``self < other``.",
246
                "__le__": "Return ``self <= other``.",  # __new__
247
                "__ne__": "Return ``self != other``.",
248
                # __reduce_ex__
249
                # __reduce__
250
                # __repr__ is defined within the function
251
                "__setattr__": "Implement :func:`setattr(self, name) <setattr>`.",
252
                "__sizeof__": "Returns the size of the object in memory, in bytes.",
253
                "__str__": "Return :class:`str(self) <str>`.",  # __subclasshook__
254
                }
255

256
# Check against dict
257
container_docstrings = {
1✔
258
                "__contains__": "Return ``key in self``.",
259
                "__getitem__": "Return ``self[key]``.",
260
                "__setitem__": "Set ``self[key]`` to ``value``.",
261
                "__delitem__": "Delete ``self[key]``.",
262
                }
263

264
# Check against int
265
operator_docstrings = {
1✔
266
                "__and__": "Return ``self & value``.",
267
                "__add__": "Return ``self + value``.",
268
                "__abs__": "Return :func:`abs(self) <abs>`.",
269
                "__divmod__": "Return :func:`divmod(self, value) <divmod>`.",
270
                "__floordiv__": "Return ``self // value``.",
271
                "__invert__": "Return ``~ self``.",
272
                "__lshift__": "Return ``self << value``.",
273
                "__mod__": "Return ``self % value``.",
274
                "__mul__": "Return ``self * value``.",
275
                "__neg__": "Return ``- self``.",
276
                "__or__": "Return ``self | value``.",
277
                "__pos__": "Return ``+ self``.",
278
                "__pow__": "Return :func:`pow(self, value, mod) <pow>`.",
279
                "__radd__": "Return ``value + self``.",
280
                "__rand__": "Return ``value & self``.",
281
                "__rdivmod__": "Return :func:`divmod(value, self) <divmod>`.",
282
                "__rfloordiv__": "Return ``value // self``.",
283
                "__rlshift__": "Return ``value << self``.",
284
                "__rmod__": "Return ``value % self``.",
285
                "__rmul__": "Return ``value * self``.",
286
                "__ror__": "Return ``value | self``.",
287
                "__rpow__": "Return :func:`pow(value, self, mod) <pow>`.",
288
                "__rrshift__": "Return ``self >> value``.",
289
                "__rshift__": "Return ``self >> value``.",
290
                "__rsub__": "Return ``value - self``.",
291
                "__rtruediv__": "Return ``value / self``.",
292
                "__rxor__": "Return ``value ^ self``.",
293
                "__sub__": "Return ``value - self``.",
294
                "__truediv__": "Return ``self / value``.",
295
                "__xor__": "Return ``self ^ value``.",
296
                }
297

298
# Check against int
299
base_int_docstrings = {
1✔
300
                # "__bool__": "Return ``self != 0``.",  # TODO
301
                # __ceil__
302
                "__float__": "Return :class:`float(self) <float>`.",  # __floor__
303
                "__int__": "Return :class:`int(self) <int>`.",  # __round__
304
                }
305

306
new_return_types = {
1✔
307
                "__eq__": bool,
308
                "__ge__": bool,
309
                "__gt__": bool,
310
                "__lt__": bool,
311
                "__le__": bool,
312
                "__ne__": bool,
313
                "__repr__": str,
314
                "__str__": str,
315
                "__int__": int,
316
                "__float__": float,
317
                "__bool__": bool,
318
                }
319

320

321
def _do_prettify(obj: Type, base: Type, new_docstrings: Dict[str, str]):
1✔
322
        """
323
        Perform the actual prettifying for :func`~.prettify_docstrings`.
324

325
        .. versionadded:: 0.8.0 (private)
326

327
        :param obj:
328
        :param base:
329
        :param new_docstrings:
330
        """
331

332
        for attr_name in new_docstrings:
1✔
333

334
                if not hasattr(obj, attr_name):
1✔
335
                        continue
1✔
336

337
                attribute = getattr(obj, attr_name)
1✔
338

339
                if not PYPY and isinstance(
1✔
340
                                attribute,
341
                                (WrapperDescriptorType, MethodDescriptorType, MethodWrapperType, MethodType),
342
                                ):
343
                        continue  # pragma: no cover (!PyPy)
344
                elif PYPY and isinstance(attribute, MethodType):
1✔
345
                        continue  # pragma: no cover
346
                elif PYPY37:  # pragma: no cover (not (PyPy and py37))
347
                        if attribute is getattr(object, attr_name, None):
348
                                continue
349
                        elif attribute is getattr(float, attr_name, None):
350
                                continue
351
                        elif attribute is getattr(str, attr_name, None):
352
                                continue
353

354
                if attribute is None:
1✔
355
                        continue
1✔
356

357
                base_docstring: Optional[str] = None
1✔
358
                if hasattr(base, attr_name):
1✔
359
                        base_docstring = getattr(base, attr_name).__doc__
1✔
360

361
                doc: Optional[str] = attribute.__doc__
1✔
362
                if doc in {None, base_docstring}:
1✔
363
                        with suppress(AttributeError, TypeError):
1✔
364
                                attribute.__doc__ = new_docstrings[attr_name]
1✔
365

366

367
def prettify_docstrings(obj: _T) -> _T:
1✔
368
        """
369
        Decorator to prettify the default :class:`object` docstrings for use in Sphinx documentation.
370

371
        .. versionadded:: 0.8.0
372

373
        :param obj: The object to prettify the method docstrings for.
374
        """
375

376
        repr_docstring = f"Return a string representation of the :class:`~{obj.__module__}.{obj.__name__}`."
1✔
377
        new_docstrings = {**base_new_docstrings, "__repr__": repr_docstring}
1✔
378

379
        _do_prettify(obj, object, new_docstrings)
1✔
380
        _do_prettify(obj, dict, container_docstrings)
1✔
381
        _do_prettify(obj, int, operator_docstrings)
1✔
382
        _do_prettify(obj, int, base_int_docstrings)
1✔
383

384
        for attribute in new_return_types:
1✔
385
                if hasattr(obj, attribute):
1✔
386
                        annotations: Dict = getattr(getattr(obj, attribute), "__annotations__", {})
1✔
387

388
                        if "return" not in annotations or annotations["return"] is Any:
1✔
389
                                annotations["return"] = new_return_types[attribute]
1✔
390

391
                        with suppress(AttributeError, TypeError):
1✔
392
                                getattr(obj, attribute).__annotations__ = annotations
1✔
393

394
        if issubclass(obj, tuple) and obj.__repr__.__doc__ == "Return a nicely formatted representation string":
1✔
395
                obj.__repr__.__doc__ = repr_docstring
1✔
396

397
        return obj
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc