Skip to content

Commit a73fb59

Browse files
authored
Support positional-only parameters in classmethods (#11635)
1 parent 02cb02c commit a73fb59

File tree

2 files changed

+49
-41
lines changed

2 files changed

+49
-41
lines changed

CHANGES

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ Dependencies
55
------------
66

77
* #11576: Require sphinxcontrib-serializinghtml 1.1.9.
8+
* #11543: autodoc: Support positional-only parameters in ``classmethod`` methods
9+
when ``autodoc_preserve_defaults`` is ``True``.
810

911
Bugs fixed
1012
----------

sphinx/ext/autodoc/preserve_defaults.py

Lines changed: 47 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -135,51 +135,57 @@ def update_defvalue(app: Sphinx, obj: Any, bound_method: bool) -> None:
135135

136136
try:
137137
args = _get_arguments(obj)
138-
if args is None:
139-
# If the object is a built-in, we won't be always able to recover
140-
# the function definition and its arguments. This happens if *obj*
141-
# is the `__init__` method generated automatically for dataclasses.
142-
return
138+
except SyntaxError:
139+
return
140+
if args is None:
141+
# If the object is a built-in, we won't be always able to recover
142+
# the function definition and its arguments. This happens if *obj*
143+
# is the `__init__` method generated automatically for dataclasses.
144+
return
145+
146+
if not args.defaults and not args.kw_defaults:
147+
return
143148

144-
if args.defaults or args.kw_defaults:
149+
try:
150+
if bound_method and inspect.ismethod(obj) and hasattr(obj, '__func__'):
151+
sig = inspect.signature(obj.__func__)
152+
else:
145153
sig = inspect.signature(obj)
146-
defaults = list(args.defaults)
147-
kw_defaults = list(args.kw_defaults)
148-
parameters = list(sig.parameters.values())
149-
for i, param in enumerate(parameters):
150-
if param.default is param.empty:
151-
if param.kind == param.KEYWORD_ONLY:
152-
# Consume kw_defaults for kwonly args
153-
kw_defaults.pop(0)
154-
else:
155-
if param.kind in (param.POSITIONAL_ONLY, param.POSITIONAL_OR_KEYWORD):
156-
default = defaults.pop(0)
157-
value = get_default_value(lines, default)
158-
if value is None:
159-
value = ast_unparse(default)
160-
parameters[i] = param.replace(default=DefaultValue(value))
161-
else:
162-
default = kw_defaults.pop(0) # type: ignore[assignment]
163-
value = get_default_value(lines, default)
164-
if value is None:
165-
value = ast_unparse(default)
166-
parameters[i] = param.replace(default=DefaultValue(value))
167-
168-
if bound_method and inspect.ismethod(obj):
169-
# classmethods
170-
cls = inspect.Parameter('cls', inspect.Parameter.POSITIONAL_OR_KEYWORD)
171-
parameters.insert(0, cls)
172-
173-
sig = sig.replace(parameters=parameters)
174-
if bound_method and inspect.ismethod(obj):
175-
# classmethods can't be assigned __signature__ attribute.
176-
obj.__dict__['__signature__'] = sig
154+
defaults = list(args.defaults)
155+
kw_defaults = list(args.kw_defaults)
156+
parameters = list(sig.parameters.values())
157+
for i, param in enumerate(parameters):
158+
if param.default is param.empty:
159+
if param.kind == param.KEYWORD_ONLY:
160+
# Consume kw_defaults for kwonly args
161+
kw_defaults.pop(0)
177162
else:
178-
obj.__signature__ = sig
163+
if param.kind in (param.POSITIONAL_ONLY, param.POSITIONAL_OR_KEYWORD):
164+
default = defaults.pop(0)
165+
value = get_default_value(lines, default)
166+
if value is None:
167+
value = ast_unparse(default)
168+
parameters[i] = param.replace(default=DefaultValue(value))
169+
else:
170+
default = kw_defaults.pop(0) # type: ignore[assignment]
171+
value = get_default_value(lines, default)
172+
if value is None:
173+
value = ast_unparse(default)
174+
parameters[i] = param.replace(default=DefaultValue(value))
175+
176+
sig = sig.replace(parameters=parameters)
177+
try:
178+
obj.__signature__ = sig
179+
except AttributeError:
180+
# __signature__ can't be set directly on bound methods.
181+
obj.__dict__['__signature__'] = sig
179182
except (AttributeError, TypeError):
180-
# failed to update signature (ex. built-in or extension types)
181-
pass
182-
except NotImplementedError as exc: # failed to ast.unparse()
183+
# Failed to update signature (e.g. built-in or extension types).
184+
# For user-defined functions, "obj" may not have __dict__,
185+
# e.g. when decorated with a class that defines __slots__.
186+
# In this case, we can't set __signature__.
187+
return
188+
except NotImplementedError as exc: # failed to ast_unparse()
183189
logger.warning(__("Failed to parse a default argument value for %r: %s"), obj, exc)
184190

185191

0 commit comments

Comments
 (0)