misc

  1import abc
  2from ctypes import Structure
  3from dataclasses import dataclass
  4import functools
  5from functools import cached_property
  6from functools import lru_cache
  7import sched
  8from typing import Generic
  9from typing import TypeVar
 10
 11# https://github.com/mitmproxy/pdoc/issues/226
 12
 13
 14class Descriptor:
 15    def __init__(self, func):
 16        self.__doc__ = func.__doc__
 17
 18    def __get__(self, instance, owner):
 19        return self if instance is None else getattr(instance, "_x", 0)
 20
 21    def __set__(self, instance, value):
 22        instance._x = value
 23
 24
 25class Issue226:
 26    @Descriptor
 27    def size(self):
 28        """This is the size"""
 29
 30
 31# Testing function and object default values
 32
 33
 34def default_func():
 35    pass
 36
 37
 38default_obj = object()
 39
 40var_with_default_obj = default_obj
 41"""this shouldn't render the object address"""
 42var_with_default_func = default_func
 43"""this just renders like a normal function"""
 44
 45
 46def func_with_defaults(a=default_obj, b=default_func):
 47    """this shouldn't render object or function addresses"""
 48    pass
 49
 50
 51# Testing classmethod links in code
 52class ClassmethodLink:
 53    """
 54    You can either do
 55
 56    >>> ClassmethodLink.bar()
 57    42
 58
 59    or
 60
 61    ```python
 62    ClassmethodLink.bar()
 63    ```
 64
 65    neither will be linked.
 66    """
 67
 68    @classmethod
 69    def bar(cls):
 70        return 42
 71
 72
 73# Testing generic bases
 74
 75T = TypeVar("T")
 76
 77
 78class GenericParent(Generic[T]):
 79    """GenericParent"""
 80
 81
 82class NonGenericChild(GenericParent[str]):
 83    """NonGenericChild"""
 84
 85
 86# Testing docstring inheritance
 87
 88
 89class Base:
 90    def __init__(self):
 91        """init"""
 92        super().__init__()
 93
 94    def foo(self):
 95        """foo"""
 96        pass
 97
 98    @classmethod
 99    def bar(cls):
100        """bar"""
101        pass
102
103    @staticmethod
104    def baz():
105        """baz"""
106        pass
107
108    @property
109    def qux(self):
110        """qux"""
111        return
112
113    @cached_property
114    def quux(self):
115        """quux"""
116        return
117
118    quuux: int = 42
119    """quuux"""
120
121
122class Child(Base):
123    def __init__(self):
124        super().__init__()
125
126    def foo(self):
127        pass
128
129    @classmethod
130    def bar(cls):
131        pass
132
133    @staticmethod
134    def baz():
135        pass
136
137    @property
138    def qux(self):
139        return
140
141    @cached_property
142    def quux(self):
143        return
144
145    quuux: int = 42
146
147
148# Testing that an attribute that is only annotated does not trigger a "submodule not found" warning.
149
150only_annotated: int
151
152
153# Testing that a private class in __all__ is displayed
154
155
156class _Private:
157    """private class"""
158
159    pass
160
161    def _do(self):
162        """private method"""
163
164
165# Testing a class attribute that is a lambda (which generates quirky sources)
166
167
168class LambdaAttr:
169    # not really supported, but also shouldn't crash.
170    attr = lambda x: 42  # noqa
171
172
173# Testing different docstring annotations
174# fmt: off
175
176
177def foo():
178    """no indents"""
179
180
181def bar():
182    """no
183indents"""
184
185
186def baz():
187    """one
188    indent"""
189
190
191def qux():
192    """
193    two
194    indents
195    """
196
197
198class Indented:
199    def foo(self):
200        """no indents"""
201
202    def bar(self):
203        """no
204indents"""
205
206    def baz(self):
207        """one
208        indent"""
209
210    def qux(self):
211        """
212        two
213        indents
214        """
215
216    @lru_cache()
217    def foo_decorated(self):
218        """no indents"""
219
220    @lru_cache()
221    # comment
222    def foo_commented(self):
223        """no indents"""
224
225    @lru_cache()
226    def bar_decorated(self):
227        """no
228indents"""
229
230    @lru_cache()
231    def baz_decorated(self):
232        """one
233        indent"""
234
235    @lru_cache()
236    def qux_decorated(self):
237        """
238        two
239        indents
240        """
241
242    @lru_cache(
243        maxsize=42
244    )
245    def quux_decorated(self):
246        """multi-line decorator, https://github.com/mitmproxy/pdoc/issues/246"""
247
248
249def _protected_decorator(f):
250    return f
251
252
253@_protected_decorator
254def fun_with_protected_decorator():
255    """This function has a protected decorator (name starting with a single `_`)."""
256
257
258class UnhashableDataDescriptor:
259    def __get__(self):
260        pass
261    __hash__ = None  # type: ignore
262
263
264unhashable = UnhashableDataDescriptor()
265
266
267class AbstractClass(metaclass=abc.ABCMeta):
268    """This class shouldn't show a constructor as it's abstract."""
269    @abc.abstractmethod
270    def foo(self):
271        pass
272
273
274# Adapted from https://github.com/mitmproxy/pdoc/issues/320
275
276def make_adder(a: int):
277    def add_func(b: int) -> int:
278        """This function adds two numbers."""
279        return a + b
280    return add_func
281
282
283add_four = make_adder(4)
284add_five = make_adder(5)
285"""This function adds five."""
286add_six = make_adder(6)
287add_six.__doc__ = "This function adds six."
288
289
290# Adapted from https://github.com/mitmproxy/pdoc/issues/335
291def linkify_links():
292    """
293    This docstring contains links that are also identifiers:
294
295    - [`linkify_links`](https://example.com/)
296    - [misc.linkify_links](https://example.com/)
297    - [`linkify_links()`](https://example.com/)
298    - [misc.linkify_links()](https://example.com/)
299    - [link in target](https://example.com/misc.linkify_links)
300    - [explicit linking](#AbstractClass.foo)
301    """
302
303
304class Issue352aMeta(type):
305    def __call__(cls, *args, **kwargs):
306        """Meta.__call__"""
307
308
309class Issue352a(metaclass=Issue352aMeta):
310    def __init__(self):
311        """Issue352.__init__ should be preferred over Meta.__call__."""
312
313
314class Issue352bMeta(type):
315    def __call__(cls, *args, **kwargs):
316        pass
317
318
319class Issue352b(metaclass=Issue352bMeta):
320    """No docstrings for the constructor here."""
321
322
323class CustomCallMeta(type):
324    def __call__(cls, *args, **kwargs):
325        """Custom docstring in metaclass.`__call__`"""
326
327
328class CustomCall(metaclass=CustomCallMeta):
329    """A class where the constructor is defined by its metaclass."""
330
331
332class Headings:
333    """
334    # Heading 1
335
336    Here is some text.
337
338    ## Heading 2
339
340    Here is some text.
341
342    ### Heading 3
343
344    Here is some text.
345
346    #### Heading 4
347
348    Here is some text.
349
350    ##### Heading 5
351
352    Here is some text.
353
354    ###### Heading 6
355
356    Here is some text.
357
358    """
359
360
361class CustomRepr:
362    def __repr__(self):
363        return "°<script>alert(1)</script>"
364
365
366def repr_not_syntax_highlightable(x=CustomRepr()):
367    """The default value for x fails to highlight with pygments."""
368
369
370class ClassDecorator:
371    """This is a class that wraps a function. It will be documented correctly."""
372    def __init__(self, func):
373        self._func = func
374
375
376@ClassDecorator
377def another_decorated_function(arg: str) -> str:
378    """This is another decorated function. It will not be documented correctly."""
379    raise NotImplementedError
380
381
382class SubclassRef:
383    class SubClass:
384        pass
385
386    def __init__(self, x: "SubClass"):
387        print(x)
388
389
390class ClassAsAttribute:
391    static_attr_to_class = ClassDecorator
392    """this is a static attribute that point to a Class (not an instance)"""
393
394    static_attr_to_instance = ClassDecorator(None)
395    """this is a static attribute that point to an instance"""
396
397
398class scheduler(sched.scheduler):
399    """Test for broken links for inherited methods, https://github.com/mitmproxy/pdoc/issues/490"""
400
401
402class __init__:
403    """https://github.com/mitmproxy/pdoc/issues/519"""
404
405
406def dynamically_modify_docstring1():
407    """this should **not** be the docstring."""
408
409
410def dynamically_modify_docstring2():
411    pass
412
413
414dynamically_modify_docstring1.__doc__ = "https://github.com/mitmproxy/pdoc/issues/536"
415dynamically_modify_docstring2.__doc__ = "https://github.com/mitmproxy/pdoc/issues/536"
416
417
418def _docstring_modifier(fn):
419    fn.__doc__ = "https://github.com/mitmproxy/pdoc/issues/536"
420    return fn
421
422
423@_docstring_modifier
424def dynamically_modify_docstring3():
425    """This should **not** be the docstring."""
426
427
428@_docstring_modifier
429def dynamically_modify_docstring4():
430    pass
431
432
433class DocstringFromNew:
434    def __new__(cls, *args, **kwargs):
435        """This is a class with a docstring inferred from `__new__`."""
436
437
438
439
440
441
442class SingleDispatchMethodExample:
443    @functools.singledispatchmethod
444    def fancymethod(self, str_or_int: str | int):
445        """A fancy method which is capable of handling either `str` or `int`.
446
447        :param str_or_int: string or integer to handle
448        """
449        raise NotImplementedError(f"{type(str_or_int)=} not implemented!")
450
451    @fancymethod.register
452    def fancymethod_handle_str(self, str_to_handle: str):
453        """Fancy method handles a string.
454
455        :param str_to_handle: string which will be handled
456        """
457        print(f"{type(str_to_handle)} = '{str_to_handle}")
458
459    @fancymethod.register
460    def _fancymethod_handle_int(self, int_to_handle: int):
461        """Fancy method handles int (not shown in doc).
462
463        :param int_to_handle: int which will be handled
464        """
465        print(f"{type(int_to_handle)} = '{int_to_handle:x}'")
466
467
468@dataclass(init=False)
469class DataclassStructure(Structure):
470    """DataclassStructure raises for `inspect.signature`."""
471
472
473class MötörCrüe:
474    "A fictional band with Unicode-characters in its name."
475
476    def créer_naïveté(self):
477        "A function with Unicode-characters in its name."
478
479
480def link_to_unicode():
481    """The following link should be created properly:
482
483    - `MötörCrüe.créer_naïveté`
484    """
485
486
487__all__ = [
488    "Issue226",
489    "var_with_default_obj",
490    "var_with_default_func",
491    "func_with_defaults",
492    "ClassmethodLink",
493    "GenericParent",
494    "NonGenericChild",
495    "Child",
496    "only_annotated",
497    "_Private",
498    "LambdaAttr",
499    "foo",
500    "bar",
501    "baz",
502    "qux",
503    "Indented",
504    "fun_with_protected_decorator",
505    "unhashable",
506    "AbstractClass",
507    "add_four",
508    "add_five",
509    "add_six",
510    "linkify_links",
511    "Issue352a",
512    "Issue352b",
513    "CustomCall",
514    "Headings",
515    "repr_not_syntax_highlightable",
516    "ClassDecorator",
517    "another_decorated_function",
518    "SubclassRef",
519    "ClassAsAttribute",
520    "scheduler",
521    "__init__",
522    "dynamically_modify_docstring1",
523    "dynamically_modify_docstring2",
524    "dynamically_modify_docstring3",
525    "dynamically_modify_docstring4",
526    "DocstringFromNew",
527    "SingleDispatchMethodExample",
528    "DataclassStructure",
529    "MötörCrüe",
530    "link_to_unicode",
531]
class Issue226:
26class Issue226:
27    @Descriptor
28    def size(self):
29        """This is the size"""
size

This is the size

var_with_default_obj = <object object>

this shouldn't render the object address

def var_with_default_func():
35def default_func():
36    pass

this just renders like a normal function

def func_with_defaults(a=<object object>, b=<function default_func>):
47def func_with_defaults(a=default_obj, b=default_func):
48    """this shouldn't render object or function addresses"""
49    pass

this shouldn't render object or function addresses

class GenericParent(typing.Generic[~T]):
79class GenericParent(Generic[T]):
80    """GenericParent"""

GenericParent

class NonGenericChild(misc.GenericParent[str]):
83class NonGenericChild(GenericParent[str]):
84    """NonGenericChild"""

NonGenericChild

class Child(Base):
123class Child(Base):
124    def __init__(self):
125        super().__init__()
126
127    def foo(self):
128        pass
129
130    @classmethod
131    def bar(cls):
132        pass
133
134    @staticmethod
135    def baz():
136        pass
137
138    @property
139    def qux(self):
140        return
141
142    @cached_property
143    def quux(self):
144        return
145
146    quuux: int = 42
Child()
124    def __init__(self):
125        super().__init__()

init

def foo(self):
127    def foo(self):
128        pass

foo

@classmethod
def bar(cls):
130    @classmethod
131    def bar(cls):
132        pass

bar

@staticmethod
def baz():
134    @staticmethod
135    def baz():
136        pass

baz

qux
138    @property
139    def qux(self):
140        return

qux

quux
142    @cached_property
143    def quux(self):
144        return

quux

quuux: int = 42

quuux

only_annotated: int
class _Private:
157class _Private:
158    """private class"""
159
160    pass
161
162    def _do(self):
163        """private method"""

private class

class LambdaAttr:
169class LambdaAttr:
170    # not really supported, but also shouldn't crash.
171    attr = lambda x: 42  # noqa
def attr(x):
171    attr = lambda x: 42  # noqa
def foo():
178def foo():
179    """no indents"""

no indents

def bar():
182def bar():
183    """no
184indents"""

no indents

def baz():
187def baz():
188    """one
189    indent"""

one indent

def qux():
192def qux():
193    """
194    two
195    indents
196    """

two indents

class Indented:
199class Indented:
200    def foo(self):
201        """no indents"""
202
203    def bar(self):
204        """no
205indents"""
206
207    def baz(self):
208        """one
209        indent"""
210
211    def qux(self):
212        """
213        two
214        indents
215        """
216
217    @lru_cache()
218    def foo_decorated(self):
219        """no indents"""
220
221    @lru_cache()
222    # comment
223    def foo_commented(self):
224        """no indents"""
225
226    @lru_cache()
227    def bar_decorated(self):
228        """no
229indents"""
230
231    @lru_cache()
232    def baz_decorated(self):
233        """one
234        indent"""
235
236    @lru_cache()
237    def qux_decorated(self):
238        """
239        two
240        indents
241        """
242
243    @lru_cache(
244        maxsize=42
245    )
246    def quux_decorated(self):
247        """multi-line decorator, https://github.com/mitmproxy/pdoc/issues/246"""
def foo(self):
200    def foo(self):
201        """no indents"""

no indents

def bar(self):
203    def bar(self):
204        """no
205indents"""

no indents

def baz(self):
207    def baz(self):
208        """one
209        indent"""

one indent

def qux(self):
211    def qux(self):
212        """
213        two
214        indents
215        """

two indents

@lru_cache()
def foo_decorated(self):
217    @lru_cache()
218    def foo_decorated(self):
219        """no indents"""

no indents

@lru_cache()
def foo_commented(self):
221    @lru_cache()
222    # comment
223    def foo_commented(self):
224        """no indents"""

no indents

@lru_cache()
def bar_decorated(self):
226    @lru_cache()
227    def bar_decorated(self):
228        """no
229indents"""

no indents

@lru_cache()
def baz_decorated(self):
231    @lru_cache()
232    def baz_decorated(self):
233        """one
234        indent"""

one indent

@lru_cache()
def qux_decorated(self):
236    @lru_cache()
237    def qux_decorated(self):
238        """
239        two
240        indents
241        """

two indents

@lru_cache(maxsize=42)
def quux_decorated(self):
243    @lru_cache(
244        maxsize=42
245    )
246    def quux_decorated(self):
247        """multi-line decorator, https://github.com/mitmproxy/pdoc/issues/246"""
def fun_with_protected_decorator():
254@_protected_decorator
255def fun_with_protected_decorator():
256    """This function has a protected decorator (name starting with a single `_`)."""

This function has a protected decorator (name starting with a single _).

def unhashable(unknown):
class AbstractClass:
268class AbstractClass(metaclass=abc.ABCMeta):
269    """This class shouldn't show a constructor as it's abstract."""
270    @abc.abstractmethod
271    def foo(self):
272        pass

This class shouldn't show a constructor as it's abstract.

@abc.abstractmethod
def foo(self):
270    @abc.abstractmethod
271    def foo(self):
272        pass
def add_four(b: int) -> int:
278    def add_func(b: int) -> int:
279        """This function adds two numbers."""
280        return a + b

This function adds two numbers.

def add_five(b: int) -> int:
278    def add_func(b: int) -> int:
279        """This function adds two numbers."""
280        return a + b

This function adds five.

def add_six(b: int) -> int:
278    def add_func(b: int) -> int:
279        """This function adds two numbers."""
280        return a + b

This function adds six.

class Issue352a:
310class Issue352a(metaclass=Issue352aMeta):
311    def __init__(self):
312        """Issue352.__init__ should be preferred over Meta.__call__."""
Issue352a()
311    def __init__(self):
312        """Issue352.__init__ should be preferred over Meta.__call__."""

Issue352.__init__ should be preferred over Meta.__call__.

class Issue352b:
320class Issue352b(metaclass=Issue352bMeta):
321    """No docstrings for the constructor here."""

No docstrings for the constructor here.

class CustomCall:
329class CustomCall(metaclass=CustomCallMeta):
330    """A class where the constructor is defined by its metaclass."""

A class where the constructor is defined by its metaclass.

CustomCall(*args, **kwargs)
325    def __call__(cls, *args, **kwargs):
326        """Custom docstring in metaclass.`__call__`"""

Custom docstring in metaclass.__call__

class Headings:
333class Headings:
334    """
335    # Heading 1
336
337    Here is some text.
338
339    ## Heading 2
340
341    Here is some text.
342
343    ### Heading 3
344
345    Here is some text.
346
347    #### Heading 4
348
349    Here is some text.
350
351    ##### Heading 5
352
353    Here is some text.
354
355    ###### Heading 6
356
357    Here is some text.
358
359    """

Heading 1

Here is some text.

Heading 2

Here is some text.

Heading 3

Here is some text.

Heading 4

Here is some text.

Heading 5

Here is some text.

Heading 6

Here is some text.

def repr_not_syntax_highlightable(x=°<script>alert(1)</script>):
367def repr_not_syntax_highlightable(x=CustomRepr()):
368    """The default value for x fails to highlight with pygments."""

The default value for x fails to highlight with pygments.

class ClassDecorator:
371class ClassDecorator:
372    """This is a class that wraps a function. It will be documented correctly."""
373    def __init__(self, func):
374        self._func = func

This is a class that wraps a function. It will be documented correctly.

ClassDecorator(func)
373    def __init__(self, func):
374        self._func = func
another_decorated_function = <ClassDecorator object>

This is another decorated function. It will not be documented correctly.

class SubclassRef:
383class SubclassRef:
384    class SubClass:
385        pass
386
387    def __init__(self, x: "SubClass"):
388        print(x)
SubclassRef(x: SubclassRef.SubClass)
387    def __init__(self, x: "SubClass"):
388        print(x)
class SubclassRef.SubClass:
384    class SubClass:
385        pass
class ClassAsAttribute:
391class ClassAsAttribute:
392    static_attr_to_class = ClassDecorator
393    """this is a static attribute that point to a Class (not an instance)"""
394
395    static_attr_to_instance = ClassDecorator(None)
396    """this is a static attribute that point to an instance"""
static_attr_to_class = <class 'ClassDecorator'>

this is a static attribute that point to a Class (not an instance)

static_attr_to_instance = <ClassDecorator object>

this is a static attribute that point to an instance

class scheduler(sched.scheduler):
399class scheduler(sched.scheduler):
400    """Test for broken links for inherited methods, https://github.com/mitmproxy/pdoc/issues/490"""

Test for broken links for inherited methods, https://github.com/mitmproxy/pdoc/issues/490

class __init__:
403class __init__:
404    """https://github.com/mitmproxy/pdoc/issues/519"""
def dynamically_modify_docstring1():
407def dynamically_modify_docstring1():
408    """this should **not** be the docstring."""
def dynamically_modify_docstring2():
411def dynamically_modify_docstring2():
412    pass
def dynamically_modify_docstring3():
424@_docstring_modifier
425def dynamically_modify_docstring3():
426    """This should **not** be the docstring."""
def dynamically_modify_docstring4():
429@_docstring_modifier
430def dynamically_modify_docstring4():
431    pass
class DocstringFromNew:
434class DocstringFromNew:
435    def __new__(cls, *args, **kwargs):
436        """This is a class with a docstring inferred from `__new__`."""
DocstringFromNew(*args, **kwargs)
435    def __new__(cls, *args, **kwargs):
436        """This is a class with a docstring inferred from `__new__`."""

This is a class with a docstring inferred from __new__.

class SingleDispatchMethodExample:
443class SingleDispatchMethodExample:
444    @functools.singledispatchmethod
445    def fancymethod(self, str_or_int: str | int):
446        """A fancy method which is capable of handling either `str` or `int`.
447
448        :param str_or_int: string or integer to handle
449        """
450        raise NotImplementedError(f"{type(str_or_int)=} not implemented!")
451
452    @fancymethod.register
453    def fancymethod_handle_str(self, str_to_handle: str):
454        """Fancy method handles a string.
455
456        :param str_to_handle: string which will be handled
457        """
458        print(f"{type(str_to_handle)} = '{str_to_handle}")
459
460    @fancymethod.register
461    def _fancymethod_handle_int(self, int_to_handle: int):
462        """Fancy method handles int (not shown in doc).
463
464        :param int_to_handle: int which will be handled
465        """
466        print(f"{type(int_to_handle)} = '{int_to_handle:x}'")
@functools.singledispatchmethod
def fancymethod(self, str_or_int: str | int):
444    @functools.singledispatchmethod
445    def fancymethod(self, str_or_int: str | int):
446        """A fancy method which is capable of handling either `str` or `int`.
447
448        :param str_or_int: string or integer to handle
449        """
450        raise NotImplementedError(f"{type(str_or_int)=} not implemented!")

A fancy method which is capable of handling either str or int.

Parameters
  • str_or_int: string or integer to handle
@fancymethod.register
def fancymethod_handle_str(self, str_to_handle: str):
452    @fancymethod.register
453    def fancymethod_handle_str(self, str_to_handle: str):
454        """Fancy method handles a string.
455
456        :param str_to_handle: string which will be handled
457        """
458        print(f"{type(str_to_handle)} = '{str_to_handle}")

Fancy method handles a string.

Parameters
  • str_to_handle: string which will be handled
@dataclass(init=False)
class DataclassStructure(_ctypes.Structure):
469@dataclass(init=False)
470class DataclassStructure(Structure):
471    """DataclassStructure raises for `inspect.signature`."""

DataclassStructure raises for inspect.signature.

class MötörCrüe:
474class MötörCrüe:
475    "A fictional band with Unicode-characters in its name."
476
477    def créer_naïveté(self):
478        "A function with Unicode-characters in its name."

A fictional band with Unicode-characters in its name.

def créer_naïveté(self):
477    def créer_naïveté(self):
478        "A function with Unicode-characters in its name."

A function with Unicode-characters in its name.