You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
When generating documentation for class with decorators that override new, documentation for the class is missing and sphinx warns that maximum recursion depth was exceeded in default conf configuration settings.
WARNING: error while formatting arguments for test.Widget: maximum recursion depth exceeded
WARNING: error while formatting signature for test.Widget: Handler <function record_typehints at 0x105504f40> for event 'autodoc-process-signature' threw an exception (exception: maximum recursion depth exceeded)
Generated doc with missing class:
Since the warning warns about an error which formatting signature, if we were to set autodoc_class_signature = "separated", a warning about recursion depth exceeded for __new__().
WARNING: error while formatting arguments for test.Widget.__new__: maximum recursion depth exceeded
WARNING: error while formatting signature for test.Widget.__new__: Handler <function record_typehints at 0x10538cf40> for event 'autodoc-process-signature' threw an exception (exception: maximum recursion depth exceeded)
Generated doc:
To get desired behavior and prevent warnings, one has to manually exclude __init__ and __new__ from the class and explicitly define them using automethod.
Desired doc:
Is this the intended workflow for decorated classes?
Given the class is functional thus implying overridden new() behavior is defined properly, why is the recursion depth exceeded during doc build?
How to Reproduce
This example is from the sqlalchemy recipe for UniqueObject and clearly reproduces this behavior.
test.py
from functools import wraps
from sqlalchemy import Column, Integer, String, create_engine
from sqlalchemy.orm import sessionmaker, scoped_session
from sqlalchemy.ext.declarative import declarative_base
def _unique(session, cls, hashfunc, queryfunc, constructor, arg, kw):
"""This is unique instance retriever."""
cache = getattr(session, "_unique_cache", None)
if cache is None:
session._unique_cache = cache = {}
key = (cls, hashfunc(*arg, **kw))
if key in cache:
return cache[key]
else:
with session.no_autoflush:
q = session.query(cls)
q = queryfunc(q, *arg, **kw)
obj = q.first()
if not obj:
obj = constructor(*arg, **kw)
session.add(obj)
cache[key] = obj
return obj
def unique_constructor(scoped_session, hashfunc, queryfunc):
"""This is unique decorator."""
def decorate(cls):
def _null_init(self, *arg, **kw):
pass
@wraps(cls)
def __new__(cls, bases, *arg, **kw):
# no-op __new__(), called
# by the loading procedure
if not arg and not kw:
return object.__new__(cls)
session = scoped_session()
def constructor(*arg, **kw):
obj = object.__new__(cls)
obj._init(*arg, **kw)
return obj
return _unique(session, cls, hashfunc, queryfunc, constructor, arg, kw)
# note: cls must be already mapped for this part to work
cls._init = cls.__init__
cls.__init__ = _null_init
cls.__new__ = classmethod(__new__)
return cls
return decorate
Base = declarative_base()
engine = create_engine("sqlite://")
Session = scoped_session(sessionmaker(bind=engine))
@unique_constructor(
Session, lambda name: name, lambda query, name: query.filter(Widget.name == name)
)
class Widget(Base):
"""This is widget class."""
__tablename__ = "widget"
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String, unique=True, nullable=False)
def __init__(self, name):
"""This is __init__ of widget."""
self.name = name
def test(self, num):
"""This is a test method.
Parameters
----------
num : int
This is a number.
"""
pass
Base.metadata.create_all(engine)
w1, w2, w3 = Widget(name="w1"), Widget(name="w2"), Widget(name="w3")
w1b = Widget(name="w1")
assert w1 is w1b
assert w2 is not w3
assert w2 is not w1
Session.commit()
working index.rst
Test
====
.. currentmodule:: test
.. autofunction:: _unique
.. autodecorator:: unique_constructor
.. autoclass:: Widget
:exclude-members: __init__, __new__
girishmm
changed the title
Documentation missing in fully-functional python class with decorator which overrides __new__()
Documentation missing for fully-functional python class with decorator which overrides __new__()
Apr 14, 2024
I'm tempted to say that we need an extension for SQLAlchemy & co. In other words, I'm not sure I want the code base to work for such cases (I honestly don't know where to start with in order to fix that...).
The thing is that the __init__ and __new__ are kind of special methods and have special handling by the ModuleAnalyzer IIRC, so I'm not really sure how to patch this... Help would be appreciated if someone has time (especially to understand where the recursion error arises).
When generating documentation for class with decorators that override
new, documentation for the class is missing and sphinx warns that
maximum recursion depth was exceeded in default conf configuration
settings. This is because `__init__` and `__new__` are kind of
special methods and have special handling by the ModuleAnalyzer. So,
the sphinx codebase does not accomodate these by default.
To work around these, changes are needed to how autodoc is processed:
- Exclude `__init__` and `__new__`
- Seperate class signature from `__init__`
Reference: sphinx-doc/sphinx#12276
Describe the bug
When generating documentation for class with decorators that override new, documentation for the class is missing and sphinx warns that maximum recursion depth was exceeded in default conf configuration settings.
Generated doc with missing class:
Since the warning warns about an error which formatting signature, if we were to set autodoc_class_signature = "separated", a warning about recursion depth exceeded for
__new__()
.Generated doc:
To get desired behavior and prevent warnings, one has to manually exclude
__init__
and__new__
from the class and explicitly define them usingautomethod
.Desired doc:
How to Reproduce
This example is from the sqlalchemy recipe for UniqueObject and clearly reproduces this behavior.
test.py
working index.rst
Environment Information
Sphinx extensions
Additional context
This is similar to Issue #12262 spare the recursion depth errors.
The text was updated successfully, but these errors were encountered: