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
Comments
TypeError: issubclass() arg 1 must be a class
when running make html
Thanks for the report, James.
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 |
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 Alternately, is there some way to tell |
What's the reST source for this example? I assume A |
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/.txtpip-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.pySome 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/docs/modules.rstrepo
====
.. toctree::
:maxdepth: 4
a Then running > 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 |
If (Btw, the signature of |
Okay @picnixz thank you for the response! I removed And yeah per 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 |
Can you show the mro of Subclass.bar? |
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 |
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?) |
Agreed. After sleeping on this, I concluded 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 My takeaway from this issue though is that with some flawed black magic, 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. |
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 |
I think somehow including 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 |
Seems reasonable -- I think I'd go for |
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? |
Yep, we can add a brief explanation in a comment and a link to this issue. |
Describe the bug
I get the below crash when running
make html
: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
Environment Information
Sphinx extensions
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.
The text was updated successfully, but these errors were encountered: