pdoc icon indicating copy to clipboard operation
pdoc copied to clipboard

pdoc trips over unittest.mock.Mock

Open Terrance opened this issue 4 years ago • 3 comments

Expected behavior

I can document classes that include mock object members (for example, when documenting testing utilities).

Actual behavior

Traceback
Traceback (most recent call last):
  File "/usr/bin/pdoc", line 33, in <module>
    sys.exit(load_entry_point('pdoc3', 'console_scripts', 'pdoc')())
  File "/usr/lib/python3.9/site-packages/pdoc/cli.py", line 575, in main
    recursive_write_files(module, ext='.html', **template_config)
  File "/usr/lib/python3.9/site-packages/pdoc/cli.py", line 351, in recursive_write_files
    recursive_write_files(submodule, ext=ext, **kwargs)
  File "/usr/lib/python3.9/site-packages/pdoc/cli.py", line 346, in recursive_write_files
    f.write(m.html(**kwargs))
  File "/usr/lib/python3.9/site-packages/pdoc/__init__.py", line 880, in html
    html = _render_template('/html.mako', module=self, **kwargs)
  File "/usr/lib/python3.9/site-packages/pdoc/__init__.py", line 155, in _render_template
    return t.render(**config).strip()
  File "/usr/lib/python3.9/site-packages/mako/template.py", line 473, in render
    return runtime._render(self, self.callable_, args, data)
  File "/usr/lib/python3.9/site-packages/mako/runtime.py", line 878, in _render
    _render_context(
  File "/usr/lib/python3.9/site-packages/mako/runtime.py", line 920, in _render_context
    _exec_template(inherit, lclcontext, args=args, kwargs=kwargs)
  File "/usr/lib/python3.9/site-packages/mako/runtime.py", line 947, in _exec_template
    callable_(context, *args, **kwargs)
  File "_html_mako", line 143, in render_body
  File "_html_mako", line 45, in show_module
  File "_html_mako", line 500, in render_show_module
  File "_html_mako", line 321, in show_func
  File "/usr/lib/python3.9/site-packages/pdoc/__init__.py", line 1431, in params
    return self._params(self, annotate=annotate, link=link, module=self.module)
  File "/usr/lib/python3.9/site-packages/pdoc/__init__.py", line 1447, in _params
    signature = inspect.signature(doc_obj.obj)
  File "/usr/lib/python3.9/inspect.py", line 3130, in signature
    return Signature.from_callable(obj, follow_wrapped=follow_wrapped)
  File "/usr/lib/python3.9/inspect.py", line 2879, in from_callable
    return _signature_from_callable(obj, sigcls=cls,
  File "/usr/lib/python3.9/inspect.py", line 2330, in _signature_from_callable
    return _signature_from_function(sigcls, obj,
  File "/usr/lib/python3.9/inspect.py", line 2173, in _signature_from_function
    positional = arg_names[:pos_count]
TypeError: 'Mock' object is not subscriptable

A naive solution would be to just not treat mocks as functions:

diff --git a/pdoc/__init__.py b/pdoc/__init__.py
index 0f05b2c..7ac7eb7 100644
--- a/pdoc/__init__.py
+++ b/pdoc/__init__.py
@@ -27,4 +27,5 @@ from typing import (  # noqa: F401
     Optional, Set, Tuple, Type, TypeVar, Union,
 )
+from unittest.mock import Mock
 from warnings import warn

@@ -411,5 +412,5 @@ def _is_public(ident_name):

 def _is_function(obj):
-    return inspect.isroutine(obj) and callable(obj)
+    return inspect.isroutine(obj) and callable(obj) and not isinstance(obj, Mock)

To reproduce

class MockService:

    async def _run(value: int) -> int: ...
    run = AsyncMock(spec=_run, side_effect=lambda value: value + 1)

Additional info

  • pdoc version: 0.10.0 / master

Terrance avatar Aug 06 '21 07:08 Terrance

This looks like one of the mock bugs in Python since inspect.signature(MockService.run) is what raises.

I guess we can apply the proposed workaround. Can you make it a pull request?

kernc avatar Aug 09 '21 02:08 kernc

+    return inspect.isroutine(obj) and callable(obj) and not isinstance(obj, Mock)

This will then fall through to interpreting MockService.run as a non-callable variable. How do we feel about that?

kernc avatar Aug 09 '21 13:08 kernc

For my use case it's acceptable enough -- I only really need the bare minimum for documentation on these classes (namely the class docstring), and having non-descript members for mock objects at least indicates they're present.

Ideally it'd show the signature of the mock's function spec if it has one, though that doesn't seem to be easily accessible (Mock._spec_signature), and there's probably a whole rabbit hole of other mock "types" to consider. Can we just identify it as a Mock object somehow?

Terrance avatar Aug 09 '21 16:08 Terrance