Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Crash with TypeError: issubclass() arg 1 must be a class when running make html #11654

Closed
jamesbraza opened this issue Aug 28, 2023 · 15 comments · Fixed by #11660
Closed

Crash with TypeError: issubclass() arg 1 must be a class when running make html #11654

jamesbraza opened this issue Aug 28, 2023 · 15 comments · Fixed by #11660

Comments

@jamesbraza
Copy link
Contributor

jamesbraza commented Aug 28, 2023

Describe the bug

I get the below crash when running make html:

  File "/Users/user/code/package/venv/lib/python3.11/site-packages/sphinx/ext/autodoc/__init__.py", line 1916, in can_document_member
    return isinstance(member, type) and issubclass(member, BaseException)
                                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TypeError: issubclass() arg 1 must be a class

I am not even sure how to root cause what's failing, as it doesn't provide me any context (even in logs).

How to Reproduce

Here is the log file's full output:

full crash log
# Platform:         darwin; (macOS-12.6-arm64-arm-64bit)
# Sphinx version:   7.2.4
# Python version:   3.11.4 (CPython)
# Docutils version: 0.18.1
# Jinja2 version:   3.1.2
# Pygments version: 2.16.1

# Last messages:
#   modules
#   
#   �[2K
#   reading sources... [ 75%]
#   package
#   
#   �[2K
#   reading sources... [100%]
#   package.namespace
#   

# Loaded extensions:
#   sphinx.ext.mathjax (7.2.4)
#   alabaster (0.7.13)
#   sphinxcontrib.applehelp (1.0.7)
#   sphinxcontrib.devhelp (1.0.5)
#   sphinxcontrib.htmlhelp (2.0.4)
#   sphinxcontrib.serializinghtml (1.1.9)
#   sphinxcontrib.qthelp (1.0.6)
#   myst_parser (2.0.0)
#   sphinx.ext.autodoc.preserve_defaults (7.2.4)
#   sphinx.ext.autodoc.type_comment (7.2.4)
#   sphinx.ext.autodoc.typehints (7.2.4)
#   sphinx.ext.autodoc (7.2.4)
#   sphinx.ext.napoleon (7.2.4)
#   sphinx.ext.viewcode (7.2.4)
#   sphinxcontrib.jquery (4.1)
#   sphinx_rtd_theme (unknown version)

# Traceback:
Traceback (most recent call last):
  File "/Users/user/code/package/venv/lib/python3.11/site-packages/sphinx/cmd/build.py", line 298, in build_main
    app.build(args.force_all, args.filenames)
  File "/Users/user/code/package/venv/lib/python3.11/site-packages/sphinx/application.py", line 355, in build
    self.builder.build_update()
  File "/Users/user/code/package/venv/lib/python3.11/site-packages/sphinx/builders/__init__.py", line 293, in build_update
    self.build(to_build,
  File "/Users/user/code/package/venv/lib/python3.11/site-packages/sphinx/builders/__init__.py", line 313, in build
    updated_docnames = set(self.read())
                           ^^^^^^^^^^^
  File "/Users/user/code/package/venv/lib/python3.11/site-packages/sphinx/builders/__init__.py", line 420, in read
    self._read_serial(docnames)
  File "/Users/user/code/package/venv/lib/python3.11/site-packages/sphinx/builders/__init__.py", line 441, in _read_serial
    self.read_doc(docname)
  File "/Users/user/code/package/venv/lib/python3.11/site-packages/sphinx/builders/__init__.py", line 498, in read_doc
    publisher.publish()
  File "/Users/user/code/package/venv/lib/python3.11/site-packages/docutils/core.py", line 217, in publish
    self.document = self.reader.read(self.source, self.parser,
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/user/code/package/venv/lib/python3.11/site-packages/sphinx/io.py", line 105, in read
    self.parse()
  File "/Users/user/code/package/venv/lib/python3.11/site-packages/docutils/readers/__init__.py", line 78, in parse
    self.parser.parse(self.input, document)
  File "/Users/user/code/package/venv/lib/python3.11/site-packages/sphinx/parsers.py", line 81, in parse
    self.statemachine.run(inputlines, document, inliner=self.inliner)
  File "/Users/user/code/package/venv/lib/python3.11/site-packages/docutils/parsers/rst/states.py", line 170, in run
    results = StateMachineWS.run(self, input_lines, input_offset,
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/user/code/package/venv/lib/python3.11/site-packages/docutils/statemachine.py", line 240, in run
    context, next_state, result = self.check_line(
                                  ^^^^^^^^^^^^^^^^
  File "/Users/user/code/package/venv/lib/python3.11/site-packages/docutils/statemachine.py", line 452, in check_line
    return method(match, context, next_state)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/user/code/package/venv/lib/python3.11/site-packages/docutils/parsers/rst/states.py", line 2779, in underline
    self.section(title, source, style, lineno - 1, messages)
  File "/Users/user/code/package/venv/lib/python3.11/site-packages/docutils/parsers/rst/states.py", line 327, in section
    self.new_subsection(title, lineno, messages)
  File "/Users/user/code/package/venv/lib/python3.11/site-packages/docutils/parsers/rst/states.py", line 393, in new_subsection
    newabsoffset = self.nested_parse(
                   ^^^^^^^^^^^^^^^^^^
  File "/Users/user/code/package/venv/lib/python3.11/site-packages/docutils/parsers/rst/states.py", line 281, in nested_parse
    state_machine.run(block, input_offset, memo=self.memo,
  File "/Users/user/code/package/venv/lib/python3.11/site-packages/docutils/parsers/rst/states.py", line 196, in run
    results = StateMachineWS.run(self, input_lines, input_offset)
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/user/code/package/venv/lib/python3.11/site-packages/docutils/statemachine.py", line 240, in run
    context, next_state, result = self.check_line(
                                  ^^^^^^^^^^^^^^^^
  File "/Users/user/code/package/venv/lib/python3.11/site-packages/docutils/statemachine.py", line 452, in check_line
    return method(match, context, next_state)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/user/code/package/venv/lib/python3.11/site-packages/docutils/parsers/rst/states.py", line 2779, in underline
    self.section(title, source, style, lineno - 1, messages)
  File "/Users/user/code/package/venv/lib/python3.11/site-packages/docutils/parsers/rst/states.py", line 327, in section
    self.new_subsection(title, lineno, messages)
  File "/Users/user/code/package/venv/lib/python3.11/site-packages/docutils/parsers/rst/states.py", line 393, in new_subsection
    newabsoffset = self.nested_parse(
                   ^^^^^^^^^^^^^^^^^^
  File "/Users/user/code/package/venv/lib/python3.11/site-packages/docutils/parsers/rst/states.py", line 281, in nested_parse
    state_machine.run(block, input_offset, memo=self.memo,
  File "/Users/user/code/package/venv/lib/python3.11/site-packages/docutils/parsers/rst/states.py", line 196, in run
    results = StateMachineWS.run(self, input_lines, input_offset)
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/user/code/package/venv/lib/python3.11/site-packages/docutils/statemachine.py", line 240, in run
    context, next_state, result = self.check_line(
                                  ^^^^^^^^^^^^^^^^
  File "/Users/user/code/package/venv/lib/python3.11/site-packages/docutils/statemachine.py", line 452, in check_line
    return method(match, context, next_state)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/user/code/package/venv/lib/python3.11/site-packages/docutils/parsers/rst/states.py", line 2352, in explicit_markup
    nodelist, blank_finish = self.explicit_construct(match)
                             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/user/code/package/venv/lib/python3.11/site-packages/docutils/parsers/rst/states.py", line 2364, in explicit_construct
    return method(self, expmatch)
           ^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/user/code/package/venv/lib/python3.11/site-packages/docutils/parsers/rst/states.py", line 2101, in directive
    return self.run_directive(
           ^^^^^^^^^^^^^^^^^^^
  File "/Users/user/code/package/venv/lib/python3.11/site-packages/docutils/parsers/rst/states.py", line 2151, in run_directive
    result = directive_instance.run()
             ^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/user/code/package/venv/lib/python3.11/site-packages/sphinx/ext/autodoc/directive.py", line 139, in run
    documenter.generate(more_content=self.content)
  File "/Users/user/code/package/venv/lib/python3.11/site-packages/sphinx/ext/autodoc/__init__.py", line 962, in generate
    self.document_members(all_members)
  File "/Users/user/code/package/venv/lib/python3.11/site-packages/sphinx/ext/autodoc/__init__.py", line 836, in document_members
    documenter.generate(
  File "/Users/user/code/package/venv/lib/python3.11/site-packages/sphinx/ext/autodoc/__init__.py", line 1898, in generate
    return super().generate(more_content=more_content,
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/user/code/package/venv/lib/python3.11/site-packages/sphinx/ext/autodoc/__init__.py", line 962, in generate
    self.document_members(all_members)
  File "/Users/user/code/package/venv/lib/python3.11/site-packages/sphinx/ext/autodoc/__init__.py", line 1884, in document_members
    super().document_members(all_members)
  File "/Users/user/code/package/venv/lib/python3.11/site-packages/sphinx/ext/autodoc/__init__.py", line 819, in document_members
    classes = [cls for cls in self.documenters.values()
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/user/code/package/venv/lib/python3.11/site-packages/sphinx/ext/autodoc/__init__.py", line 820, in <listcomp>
    if cls.can_document_member(member, mname, isattr, self)]
       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/user/code/package/venv/lib/python3.11/site-packages/sphinx/ext/autodoc/__init__.py", line 1916, in can_document_member
    return isinstance(member, type) and issubclass(member, BaseException)
                                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TypeError: issubclass() arg 1 must be a class

Environment Information

Platform:              darwin; (macOS-12.6-arm64-arm-64bit)
Python version:        3.11.4 (main, Jul 13 2023, 12:19:06) [Clang 14.0.0 (clang-1400.0.29.202)])
Python implementation: CPython
Sphinx version:        7.2.4
Docutils version:      0.18.1
Jinja2 version:        3.1.2
Pygments version:      2.16.1

Sphinx extensions

extensions = [
    "myst_parser",
    "sphinx.ext.autodoc",
    "sphinx.ext.napoleon",
    "sphinx.ext.viewcode",
]

Additional context

I have a native namespace package, which could be part of the issue.

The source code is a private repository, so I can't share a link to it.

@jamesbraza jamesbraza changed the title Crash with TypeError: issubclass() arg 1 must be a class Crash with TypeError: issubclass() arg 1 must be a class when running make html Aug 28, 2023
@AA-Turner
Copy link
Member

Thanks for the report, James.

The source code is a private repository, so I can't share a link to it.

Are you able to create a minimal generic example that still fails? I'd bisect by deleting half of the documentation at a time and seeing if the error remains.

I can't provide free community support without such a reproducer, unfortunately.

A

@AA-Turner AA-Turner added extensions:autodoc awaiting:response Waiting for a response from the author of this issue labels Aug 28, 2023
@jamesbraza
Copy link
Contributor Author

I have played for a while, and narrowed it down to it breaking on this:

from abc import ABCMeta

class PMetaclass(ABCMeta):
    def __init__(cls, name, bases, attrs):
        super().__init__(name, bases, attrs)
        props = {name: attr for name, attr in attrs.items() if isinstance(attr, P)}
        for p in props.values():
            p.__bind__(cls)

class P:
    def __init__(self):
        self.__proxies: dict[PMetaclass, P._ClassProxy] = {}

    def __class_getitem__(cls):
        return ABCMeta(cls.__name__, (cls,), {"__module__": cls.__module__})

    def __bind__(self, declaring_type):
        self.__bind_subclass__(declaring_type)

    def __bind_subclass__(self, subclass):
        self.__proxies[subclass] = self._ClassProxy(self, subclass)

    def __get__(self, instance, owner):
        if instance is None:
            try:
                return self.__proxies[owner]
            except KeyError:
                return self
        return None

    class _ClassProxy:
        def __new__(cls, p, pcls: PMetaclass):
            return (
                type(
                    type(p).__name__,
                    (cls,),
                    {
                        "__module__": type(p).__module__,
                        "__class__": type(p).__class__,
                    },
                )(p, pcls)
                if cls is P._ClassProxy
                else super().__new__(cls)
            )

class PClass(metaclass=PMetaclass):
    pass

class Subclass(PClass):
    bar = P()

I cut out a LOT of stuff, so the code doesn't really make sense any more, it's more just a source of the error.

I think what should happen is the issubclass in ExceptionDocumenter.can_document_member can throw a TypeError, so it should be caught and return False.

Alternately, is there some way to tell autodoc to "not look at" imported sections of code for docstrings?

@AA-Turner
Copy link
Member

What's the reST source for this example? I assume .. autoclass:: Subclass?

A

@jamesbraza
Copy link
Contributor Author

jamesbraza commented Aug 29, 2023

Okay, sorry this should have been in the original issue, I was being lazy 😄 .

Here is a complete minimal repro of the issue:

repo/docs/requirements.in/.txt
pip-tools
setuptools<61  # For setuptools.version still being present
sphinx
sphinx_rtd_theme
#
# This file is autogenerated by pip-compile with Python 3.11
# by the following command:
#
#    pip-compile --allow-unsafe --no-emit-index-url
#
alabaster==0.7.13
    # via sphinx
babel==2.12.1
    # via sphinx
build==0.10.0
    # via pip-tools
certifi==2023.7.22
    # via requests
charset-normalizer==3.2.0
    # via requests
click==8.1.7
    # via pip-tools
docutils==0.18.1
    # via
    #   sphinx
    #   sphinx-rtd-theme
idna==3.4
    # via requests
imagesize==1.4.1
    # via sphinx
jinja2==3.1.2
    # via sphinx
markupsafe==2.1.3
    # via jinja2
packaging==23.1
    # via
    #   build
    #   sphinx
pip-tools==7.3.0
    # via -r requirements.in
pygments==2.16.1
    # via sphinx
pyproject-hooks==1.0.0
    # via build
requests==2.31.0
    # via sphinx
snowballstemmer==2.2.0
    # via sphinx
sphinx==7.2.4
    # via
    #   -r requirements.in
    #   sphinx-rtd-theme
    #   sphinxcontrib-applehelp
    #   sphinxcontrib-devhelp
    #   sphinxcontrib-htmlhelp
    #   sphinxcontrib-jquery
    #   sphinxcontrib-qthelp
    #   sphinxcontrib-serializinghtml
sphinx-rtd-theme==1.3.0
    # via -r requirements.in
sphinxcontrib-applehelp==1.0.7
    # via sphinx
sphinxcontrib-devhelp==1.0.5
    # via sphinx
sphinxcontrib-htmlhelp==2.0.4
    # via sphinx
sphinxcontrib-jquery==4.1
    # via sphinx-rtd-theme
sphinxcontrib-jsmath==1.0.1
    # via sphinx
sphinxcontrib-qthelp==1.0.6
    # via sphinx
sphinxcontrib-serializinghtml==1.1.9
    # via sphinx
urllib3==2.0.4
    # via requests
wheel==0.41.2
    # via pip-tools

# The following packages are considered to be unsafe in a requirements file:
pip==23.2.1
    # via pip-tools
setuptools==60.10.0
    # via
    #   -r requirements.in
    #   pip-tools
repo/docs/conf.py
# Configuration file for the Sphinx documentation builder.
#
# For the full list of built-in configuration values, see the documentation:
# https://www.sphinx-doc.org/en/master/usage/configuration.html

# -- Path setup --------------------------------------------------------------
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.

import os
import sys

CONF_FILE_PATH = __file__
REPO_ROOT_PATH = os.path.join(os.path.dirname(CONF_FILE_PATH), "..")
sys.path.append(REPO_ROOT_PATH)

# -- Project information -----------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information

project = "repo"
copyright = "2023, Foo Bar"
author = "Foo Bar"

# -- General configuration ---------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration

extensions = [
    "sphinx.ext.autodoc",
    "sphinx.ext.napoleon",
    "sphinx.ext.viewcode",
]

templates_path = ["_templates"]
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]


# -- Options for HTML output -------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output

html_theme = "sphinx_rtd_theme"
html_static_path = ["_static"]
repo/a.py

Some updates were made since the OP:

from abc import ABCMeta


class PMetaclass(ABCMeta):
    def __init__(cls, name, bases, attrs):
        super().__init__(name, bases, attrs)
        props = {name: attr for name, attr in attrs.items() if isinstance(attr, P)}
        for p in props.values():
            p.__bind__(cls)


class P:
    def __init__(self):
        self.__proxies: dict[PMetaclass, P._ClassProxy] = {}

    def __class_getitem__(cls):
        return ABCMeta(cls.__name__, (cls,), {"__module__": cls.__module__})

    def __bind__(self, declaring_type):
        self.__proxies[declaring_type] = self._ClassProxy(self, declaring_type)

    def __get__(self, instance, owner):
        if instance is None:
            return self.__proxies.get(owner, self)
        return None

    class _ClassProxy:
        def __new__(cls, p, pcls: PMetaclass):
            return (
                type(
                    type(p).__name__,
                    (cls,),
                    {
                        "__module__": type(p).__module__,
                        "__class__": type(p).__class__,
                    },
                )(p, pcls)
                if cls is P._ClassProxy
                else super().__new__(cls)
            )


class PClass(metaclass=PMetaclass):
    pass


class Subclass(PClass):
    bar = P()

From repo, run: sphinx-apidoc --force -o docs .

repo/docs/modules.rst
repo
====

.. toctree::
   :maxdepth: 4

   a

Then running sphinx-build docs docs/_build:

> sphinx-build docs docs/_build
Running Sphinx v7.2.4
myst v2.0.0: MdParserConfig(commonmark_only=False, gfm_only=False, enable_extensions=set(), disable_syntax=[], all_links_external=False, url_schemes=('http', 'https', 'mailto', 'ftp'), ref_domains=None, fence_as_directive=set(), number_code_blocks=[], title_to_header=False, heading_anchors=0, heading_slug_func=None, html_meta={}, footnote_transition=True, words_per_minute=200, substitutions={}, linkify_fuzzy_links=True, dmath_allow_labels=True, dmath_allow_space=True, dmath_allow_digits=True, dmath_double_inline=False, update_mathjax=True, mathjax_classes='tex2jax_process|mathjax_process|math|output_area', enable_checkboxes=False, suppress_warnings=[], highlight_code_blocks=True)
building [mo]: targets for 0 po files that are out of date
writing output...
building [html]: targets for 3 source files that are out of date
updating environment: [new config] 3 added, 0 changed, 0 removed
reading sources... [ 33%] a
Exception occurred:
  File "/Users/user/code/repo/venv/lib/python3.11/site-packages/sphinx/ext/autodoc/__init__.py", line 1916, in can_document_member
    return isinstance(member, type) and issubclass(member, BaseException)
                                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TypeError: issubclass() arg 1 must be a class
The full traceback has been saved in /var/folders/78/lm6p91s90fx99cshsxqz_19w0000gn/T/sphinx-err-vzf_jmc2.log, if you want to report the issue to the developers.
Please also report this if it was a user error, so that a better error message can be provided next time.
A bug report can be filed in the tracker at <https://github.com/sphinx-doc/sphinx/issues>. Thanks!

Let me know what I can do to help 👍 . Also, if there's some way I can stop sphinx-build from recursing into the descriptor P, let me know.

@picnixz
Copy link
Member

picnixz commented Aug 29, 2023

If isinstance(..., type) passes then issubclass shouldn't fail (because issubclass should only raise if the object is not a type IIRC). Now, I am wondering which object is actually responsible for the issue. Can you add the classes one by one and check when the error happens (replace _ClassProxy by a dummy function, this class may be the issue)? also, can you remove the myst_parser` extension? (not sure it will change anything but better to keep it simple)

(Btw, the signature of __class_getitem__ should include a second parameter.)

@jamesbraza
Copy link
Contributor Author

Okay @picnixz thank you for the response!

I removed myst_parser from extensions/requirements in my above minimal repro comment, you're right it was extra.

And yeah per __class_getitem__ needing a second item arg, I was just removing all unused args. Adding in a placeholder item argument doesn't fix the issue fwiw. I think you're right it comes from _ClassProxy.

I think the error is summarized by this:

In [1]: isinstance(Subclass.bar, type)
Out[1]: True
In [2]: issubclass(Subclass.bar, BaseException)
Traceback (most recent call last):
  File "/path/to/venv/lib/python3.11/site-packages/IPython/core/interactiveshell.py", line 3508, in run_code
    exec(code_obj, self.user_global_ns, self.user_ns)
  File "<ipython-input-2-7d0541bf8c19>", line 1, in <module>
    issubclass(Subclass.bar, BaseException)
TypeError: issubclass() arg 1 must be a class

I think this is somehow a case where isinstance(..., type) returns True, but then issubclass blows up. Perhaps it's a bug in Python

@picnixz
Copy link
Member

picnixz commented Aug 29, 2023

Can you show the mro of Subclass.bar?

@jamesbraza
Copy link
Contributor Author

Here it is:

In[1]: type(Subclass.bar).__mro__
Out[1]: (__main__.P, __main__.P._ClassProxy, object)
In [2]: Subclass.bar.__mro__
Traceback (most recent call last):
  File "/path/to/venv/lib/python3.11/site-packages/IPython/core/interactiveshell.py", line 3508, in run_code
    exec(code_obj, self.user_global_ns, self.user_ns)
  File "<ipython-input-3-f75bc28e7e4f>", line 1, in <module>
    Subclass.bar.__mro__
AttributeError: 'P' object has no attribute '__mro__'

Also, I edited my commented minimal repro above to be marginally simpler

@picnixz
Copy link
Member

picnixz commented Aug 29, 2023

I think the issue is because of how you create the local class in ClassProxy and the arguments. Can you change the implementation of ClassProxy (using a real class) and possibly by not having this if-then-else but instead use a function to create an instance of a subclass of ClassProxy? (and maybe the issue is with the local class name + module that is clashing?)

@jamesbraza
Copy link
Contributor Author

(and maybe the issue is with the local class name + module that is clashing?)

Agreed. After sleeping on this, I concluded isinstance(Subclass.bar, type) should be returning False, as it's not a type but a descriptor instance. This can be fixed by removing the __class__ propagation:

    class _ClassProxy:
        def __new__(cls, p, pcls: PMetaclass):
            return (
                type(
                    type(p).__name__,
                    (cls,),
                    {
                        "__module__": type(p).__module__,
                        "__class__": type(p).__class__,  # Remove this line
                    },
                )(p, pcls)
                if cls is P._ClassProxy
                else super().__new__(cls)
            )

Now, the first isinstance fails, which short circuits the issubclass check within autodoc. So my use case no longer triggers a crash in sphinx-build 🥳 .


My takeaway from this issue though is that with some flawed black magic, isinstance(member, type) can be True but member not be a class, which will break issubclass.

Do you think there should be a change made in Sphinx to at least not crash here? Or it's so niche, that it's better to crash.

@AA-Turner
Copy link
Member

flawed black magic

Generally Python uses the consenting adults principle -- it is possible to break things in many ways by subverting the expected contracts (metaclasses and descriptors are often involved!).

I'd accept a patch to fail with a nicer error message if you have time to write one, but I'm unlikely to have time to write one myself.

(Thanks for working out what was at fault here, sorry I didn't have time to look in to this before now)

A

@jamesbraza
Copy link
Contributor Author

I think somehow including member in the message could be useful, something like this:

class ExceptionDocumenter(ClassDocumenter):
    ...

    @classmethod
    def can_document_member(
        cls, member: Any, membername: str, isattr: bool, parent: Any
    ) -> bool:
        try:
            return isinstance(member, type) and issubclass(member, BaseException)
        except TypeError as exc:
            raise LookupError(
                f"{cls.__name__} failed to discern if {member} is a BaseException subclass."
            ) from exc

(and wdyt of LookupError as the exc type)

@AA-Turner
Copy link
Member

Seems reasonable -- I think I'd go for ValueError though -- LookupError seems more used for __getitem__ based errors.

@jamesbraza
Copy link
Contributor Author

Okay one more quick question, I don't think this "minimal repro" deserves to be added to test suite. Are you okay with making this change without adding a test case for the re-raising?

@AA-Turner
Copy link
Member

Yep, we can add a brief explanation in a comment and a link to this issue.

@picnixz picnixz removed the awaiting:response Waiting for a response from the author of this issue label Aug 30, 2023
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Sep 30, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants