Skip to content

Commit 232182b

Browse files
committed
Add new prettify_docstrings function to doctools.
1 parent f618212 commit 232182b

File tree

2 files changed

+342
-1
lines changed

2 files changed

+342
-1
lines changed

domdf_python_tools/doctools.py

Lines changed: 152 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,10 @@
2929
# stdlib
3030
import builtins
3131
from textwrap import dedent
32-
from typing import Any, Callable, Optional, Sequence, Type, TypeVar, Union
32+
from typing import Any, Callable, Dict, Optional, Sequence, Type, TypeVar, Union
33+
34+
# this package
35+
from domdf_python_tools.typing import MethodDescriptorType, MethodWrapperType, WrapperDescriptorType
3336

3437
__all__ = [
3538
"F",
@@ -40,6 +43,7 @@
4043
"is_documented_by",
4144
"append_docstring_from",
4245
"sphinxify_docstring",
46+
"prettify_docstrings",
4347
]
4448

4549
F = TypeVar('F', bound=Callable[..., Any])
@@ -203,3 +207,150 @@ def wrapper(target: F) -> F:
203207
return target
204208

205209
return wrapper
210+
211+
212+
# Check against object
213+
base_new_docstrings = {
214+
"__delattr__": "Implement :func:`delattr(self, name) <delattr>`.",
215+
"__dir__": "Default :func:`dir` implementation.",
216+
"__eq__": "Return ``self == other``.", # __format__
217+
"__getattribute__": "Return :func:`getattr(self, name) <getattr>`.",
218+
"__ge__": "Return ``self >= other``.",
219+
"__gt__": "Return ``self > other``.",
220+
"__hash__": "Return :func:`hash(self) <hash>`.",
221+
# __init_subclass__
222+
# __init__ # not usually shown in sphinx
223+
"__lt__": "Return ``self < other``.",
224+
"__le__": "Return ``self <= other``.", # __new__
225+
"__ne__": "Return ``self != other``.",
226+
# __reduce_ex__
227+
# __reduce__
228+
# __repr__ is defined within the function
229+
"__setattr__": "Implement :func:`setattr(self, name) <setattr>`.",
230+
"__sizeof__": "Returns the size of the object in memory, in bytes.",
231+
"__str__": "Return :func:`str(self) <str>`.", # __subclasshook__
232+
}
233+
234+
# Check against dict
235+
container_docstrings = {
236+
"__contains__": "Return ``key in self``.",
237+
"__getitem__": "Return ``self[key]``.",
238+
"__setitem__": "Set ``self[key]`` to ``value``.",
239+
"__delitem__": "Delete ``self[key]````.",
240+
}
241+
242+
# Check against int
243+
operator_docstrings = {
244+
"__and__": "Return ``self & value``.",
245+
"__add__": "Return ``self + value``.",
246+
"__abs__": "Return :func:`abs(self) <abs>`.",
247+
"__divmod__": "Return :func:`divmod(self, value) <divmod>`.",
248+
"__floordiv__": "Return ``self // value``.",
249+
"__invert__": "Return ``~ self``.",
250+
"__lshift__": "Return ``self << value``.",
251+
"__mod__": "Return ``self % value``.",
252+
"__mul__": "Return ``self * value``.",
253+
"__neg__": "Return ``- self``.",
254+
"__or__": "Return ``self | value``.",
255+
"__pos__": "Return ``+ self``.",
256+
"__pow__": "Return :func:`pow(self, value, mod) <pow>`.",
257+
"__radd__": "Return ``value + self``.",
258+
"__rand__": "Return ``value & self``.",
259+
"__rdivmod__": "Return :func:`divmod(value, self) <divmod>`.",
260+
"__rfloordiv__": "Return ``value // self``.",
261+
"__rlshift__": "Return ``value << self``.",
262+
"__rmod__": "Return ``value % self``.",
263+
"__rmul__": "Return ``value * self``.",
264+
"__ror__": "Return ``value | self``.",
265+
"__rpow__": "Return :func:`pow(value, self, mod) <pow>`.",
266+
"__rrshift__": "Return ``self >> value``.",
267+
"__rshift__": "Return ``self >> value``.",
268+
"__rsub__": "Return ``value - self``.",
269+
"__rtruediv__": "Return ``value / self``.",
270+
"__rxor__": "Return ``value ^ self``.",
271+
"__sub__": "Return ``value - self``.",
272+
"__truediv__": "Return ``self / value``.",
273+
"__xor__": "Return ``self ^ value``.",
274+
}
275+
276+
# Check against int
277+
base_int_docstrings = {
278+
# "__bool__": "Return ``self != 0`.", # TODO
279+
# __ceil__
280+
"__float__": "Return :func:`float(self) <float>`.", # __floor__
281+
"__int__": "Return :func:`int(self) <int>`.", # __round__
282+
}
283+
284+
new_return_types = {
285+
"__eq__": bool,
286+
"__ge__": bool,
287+
"__gt__": bool,
288+
"__lt__": bool,
289+
"__le__": bool,
290+
"__ne__": bool,
291+
"__repr__": str,
292+
"__str__": str,
293+
"__int__": int,
294+
"__float__": float,
295+
"__bool__": bool,
296+
}
297+
298+
299+
def _do_prettify(obj: Type, base: Type, new_docstrings: Dict[str, str]):
300+
"""
301+
Perform the actual prettification for :func`~.prettify_docstrings`.
302+
303+
:param obj:
304+
:param base:
305+
:param new_docstrings:
306+
307+
:return:
308+
"""
309+
310+
for attr_name in new_docstrings:
311+
312+
if hasattr(obj, attr_name):
313+
attribute = getattr(obj, attr_name)
314+
315+
if not isinstance(attribute, (WrapperDescriptorType, MethodDescriptorType, MethodWrapperType)):
316+
317+
doc: Optional[str] = getattr(obj, attr_name).__doc__
318+
319+
if doc in {None, getattr(base, attr_name).__doc__}:
320+
getattr(obj, attr_name).__doc__ = new_docstrings[attr_name]
321+
322+
323+
def prettify_docstrings(obj: Type) -> Type:
324+
"""
325+
Prettify the default :class:`object` docstrings for use in Sphinx documentation.
326+
327+
:param obj: The object to prettify the docstrings for.
328+
329+
:return: The object
330+
331+
Default :func:`dir` implementation.
332+
"""
333+
334+
new_docstrings = {
335+
**base_new_docstrings,
336+
"__repr__": f"Return a string representation of the :class:`~{obj.__module__}.{obj.__name__}`.",
337+
}
338+
339+
_do_prettify(obj, object, new_docstrings)
340+
_do_prettify(obj, dict, container_docstrings)
341+
_do_prettify(obj, int, operator_docstrings)
342+
_do_prettify(obj, int, base_int_docstrings)
343+
344+
for attribute in new_return_types:
345+
if hasattr(obj, attribute):
346+
annotations: Dict = getattr(getattr(obj, attribute), "__annotations__", {})
347+
348+
if "return" not in annotations or annotations["return"] is Any:
349+
annotations["return"] = new_return_types[attribute]
350+
351+
try:
352+
getattr(obj, attribute).__annotations__ = annotations
353+
except AttributeError: # pragma: no cover
354+
pass
355+
356+
return obj

tests/test_doctools.py

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,18 @@
1010
import math
1111

1212
# 3rd party
13+
from typing import get_type_hints
14+
1315
import pytest
1416

1517
# this package
1618
from domdf_python_tools import doctools
1719

1820
# TODO: test sphinxification of docstrings
21+
from domdf_python_tools.doctools import (
22+
base_int_docstrings, base_new_docstrings, container_docstrings, operator_docstrings,
23+
prettify_docstrings,
24+
)
1925

2026

2127
class Cafe:
@@ -339,3 +345,187 @@ def demo_function():
339345
:return: pi
340346
:rtype: float
341347
"""
348+
349+
350+
351+
@prettify_docstrings
352+
class Klasse:
353+
def __delattr__(self, **kwargs):
354+
... # pragma: no cover
355+
356+
def __dir__(self):
357+
... # pragma: no cover
358+
359+
def __eq__(self, **kwargs):
360+
... # pragma: no cover
361+
362+
def __getattribute__(self, **kwargs):
363+
... # pragma: no cover
364+
365+
def __ge__(self):
366+
... # pragma: no cover
367+
368+
def __gt__(self):
369+
... # pragma: no cover
370+
371+
def __hash__(self):
372+
... # pragma: no cover
373+
374+
def __lt__(self):
375+
... # pragma: no cover
376+
377+
def __le__(self):
378+
... # pragma: no cover
379+
380+
def __ne__(self, **kwargs):
381+
... # pragma: no cover
382+
383+
def __setattr__(self, **kwargs):
384+
... # pragma: no cover
385+
386+
def __sizeof__(self):
387+
... # pragma: no cover
388+
389+
def __str__(self):
390+
... # pragma: no cover
391+
392+
def __contains__(self):
393+
... # pragma: no cover
394+
395+
def __getitem__(self):
396+
... # pragma: no cover
397+
398+
def __setitem__(self):
399+
... # pragma: no cover
400+
401+
def __delitem__(self):
402+
... # pragma: no cover
403+
404+
def __and__(self):
405+
... # pragma: no cover
406+
407+
def __add__(self):
408+
... # pragma: no cover
409+
410+
def __abs__(self):
411+
... # pragma: no cover
412+
413+
def __divmod__(self):
414+
... # pragma: no cover
415+
416+
def __floordiv__(self):
417+
... # pragma: no cover
418+
419+
def __invert__(self):
420+
... # pragma: no cover
421+
422+
def __lshift__(self):
423+
... # pragma: no cover
424+
425+
def __mod__(self):
426+
... # pragma: no cover
427+
428+
def __mul__(self):
429+
... # pragma: no cover
430+
431+
def __neg__(self):
432+
... # pragma: no cover
433+
434+
def __or__(self):
435+
... # pragma: no cover
436+
437+
def __pos__(self):
438+
... # pragma: no cover
439+
440+
def __pow__(self):
441+
... # pragma: no cover
442+
443+
def __radd__(self):
444+
... # pragma: no cover
445+
446+
def __rand__(self):
447+
... # pragma: no cover
448+
449+
def __rdivmod__(self):
450+
... # pragma: no cover
451+
452+
def __rfloordiv__(self):
453+
... # pragma: no cover
454+
455+
def __rlshift__(self):
456+
... # pragma: no cover
457+
458+
def __rmod__(self):
459+
... # pragma: no cover
460+
461+
def __rmul__(self):
462+
... # pragma: no cover
463+
464+
def __ror__(self):
465+
... # pragma: no cover
466+
467+
def __rpow__(self):
468+
... # pragma: no cover
469+
470+
def __rrshift__(self):
471+
... # pragma: no cover
472+
473+
def __rshift__(self):
474+
... # pragma: no cover
475+
476+
def __rsub__(self):
477+
... # pragma: no cover
478+
479+
def __rtruediv__(self):
480+
... # pragma: no cover
481+
482+
def __rxor__(self):
483+
... # pragma: no cover
484+
485+
def __sub__(self):
486+
... # pragma: no cover
487+
488+
def __truediv__(self):
489+
... # pragma: no cover
490+
491+
def __xor__(self):
492+
... # pragma: no cover
493+
494+
def __float__(self):
495+
... # pragma: no cover
496+
497+
def __int__(self):
498+
... # pragma: no cover
499+
500+
def __repr__(self):
501+
... # pragma: no cover
502+
503+
def __bool__(self):
504+
... # pragma: no cover
505+
506+
507+
def test_prettify_docstrings():
508+
509+
all_docstrings = {
510+
**base_new_docstrings,
511+
**container_docstrings,
512+
**operator_docstrings,
513+
**base_int_docstrings,
514+
}
515+
516+
for attr_name, docstring in base_new_docstrings.items():
517+
assert getattr(Klasse, attr_name).__doc__ == docstring
518+
519+
assert get_type_hints(Klasse.__eq__)["return"] is bool
520+
assert get_type_hints(Klasse.__ge__)["return"] is bool
521+
assert get_type_hints(Klasse.__gt__)["return"] is bool
522+
assert get_type_hints(Klasse.__lt__)["return"] is bool
523+
assert get_type_hints(Klasse.__le__)["return"] is bool
524+
assert get_type_hints(Klasse.__ne__)["return"] is bool
525+
assert get_type_hints(Klasse.__repr__)["return"] is str
526+
assert get_type_hints(Klasse.__str__)["return"] is str
527+
assert get_type_hints(Klasse.__int__)["return"] is int
528+
assert get_type_hints(Klasse.__float__)["return"] is float
529+
assert get_type_hints(Klasse.__bool__)["return"] is bool
530+
531+
assert Klasse.__repr__.__doc__ == "Return a string representation of the :class:`~tests.test_doctools.Klasse`."

0 commit comments

Comments
 (0)