Skip to content

Commit 9173825

Browse files
committed
Fix inspection of built-in functions with >= 3.11
The built-in functions no longer have their signature in the docstring, but now inspect.signature can produce results. But as we have no source for built-in functions, we cannot replace the default values. Hence, we handle built-in functions in an extra step. This commit also changes the handling of default values slightly. They are now always put into a _Repr.
1 parent 3b89278 commit 9173825

File tree

2 files changed

+60
-36
lines changed

2 files changed

+60
-36
lines changed

bpython/inspection.py

Lines changed: 41 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,9 @@ class ArgSpec:
5757
args: List[str]
5858
varargs: Optional[str]
5959
varkwargs: Optional[str]
60-
defaults: Optional[List[Any]]
60+
defaults: Optional[List[_Repr]]
6161
kwonly: List[str]
62-
kwonly_defaults: Optional[Dict[str, Any]]
62+
kwonly_defaults: Optional[Dict[str, _Repr]]
6363
annotations: Optional[Dict[str, Any]]
6464

6565

@@ -169,31 +169,51 @@ def parsekeywordpairs(signature: str) -> Dict[str, str]:
169169
return {item[0]: "".join(item[2:]) for item in stack if len(item) >= 3}
170170

171171

172-
def _fixlongargs(f: Callable, argspec: ArgSpec) -> ArgSpec:
172+
def _fix_default_values(f: Callable, argspec: ArgSpec) -> ArgSpec:
173173
"""Functions taking default arguments that are references to other objects
174-
whose str() is too big will cause breakage, so we swap out the object
175-
itself with the name it was referenced with in the source by parsing the
176-
source itself !"""
177-
if argspec.defaults is None:
174+
will cause breakage, so we swap out the object itself with the name it was
175+
referenced with in the source by parsing the source itself!"""
176+
177+
if argspec.defaults is None and argspec.kwonly_defaults is None:
178178
# No keyword args, no need to do anything
179179
return argspec
180-
values = list(argspec.defaults)
181-
if not values:
182-
return argspec
183-
keys = argspec.args[-len(values) :]
180+
184181
try:
185-
src = inspect.getsourcelines(f)
182+
src, _ = inspect.getsourcelines(f)
186183
except (OSError, IndexError):
187184
# IndexError is raised in inspect.findsource(), can happen in
188185
# some situations. See issue #94.
189186
return argspec
190-
kwparsed = parsekeywordpairs("".join(src[0]))
187+
except TypeError:
188+
# No source code is available (for Python >= 3.11)
189+
#
190+
# If the function is a builtin, we replace the default values.
191+
# Otherwise, let's bail out.
192+
if not inspect.isbuiltin(f):
193+
raise
194+
195+
if argspec.defaults is not None:
196+
argspec.defaults = [_Repr(str(value)) for value in argspec.defaults]
197+
if argspec.kwonly_defaults is not None:
198+
argspec.kwonly_defaults = {
199+
key: _Repr(str(value))
200+
for key, value in argspec.kwonly_defaults.items()
201+
}
202+
return argspec
191203

192-
for i, (key, value) in enumerate(zip(keys, values)):
193-
if len(repr(value)) != len(kwparsed[key]):
204+
kwparsed = parsekeywordpairs("".join(src))
205+
206+
if argspec.defaults is not None:
207+
values = list(argspec.defaults)
208+
keys = argspec.args[-len(values) :]
209+
for i, key in enumerate(keys):
194210
values[i] = _Repr(kwparsed[key])
195211

196-
argspec.defaults = values
212+
argspec.defaults = values
213+
if argspec.kwonly_defaults is not None:
214+
for key in argspec.kwonly_defaults.keys():
215+
argspec.kwonly_defaults[key] = _Repr(kwparsed[key])
216+
197217
return argspec
198218

199219

@@ -234,11 +254,11 @@ def _getpydocspec(f: Callable) -> Optional[ArgSpec]:
234254
if varargs is not None:
235255
kwonly_args.append(arg)
236256
if default:
237-
kwonly_defaults[arg] = default
257+
kwonly_defaults[arg] = _Repr(default)
238258
else:
239259
args.append(arg)
240260
if default:
241-
defaults.append(default)
261+
defaults.append(_Repr(default))
242262

243263
return ArgSpec(
244264
args, varargs, varkwargs, defaults, kwonly_args, kwonly_defaults, None
@@ -267,7 +287,9 @@ def getfuncprops(func: str, f: Callable) -> Optional[FuncProps]:
267287
return None
268288
try:
269289
argspec = _get_argspec_from_signature(f)
270-
fprops = FuncProps(func, _fixlongargs(f, argspec), is_bound_method)
290+
fprops = FuncProps(
291+
func, _fix_default_values(f, argspec), is_bound_method
292+
)
271293
except (TypeError, KeyError, ValueError):
272294
argspec_pydoc = _getpydocspec(f)
273295
if argspec_pydoc is None:

bpython/test/test_inspection.py

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from bpython.test.fodder import encoding_utf8
1212

1313
pypy = "PyPy" in sys.version
14+
_is_py311 = sys.version_info[:2] >= (3, 11)
1415

1516
try:
1617
import numpy
@@ -53,38 +54,32 @@ def test_parsekeywordpairs(self):
5354
def fails(spam=["-a", "-b"]):
5455
pass
5556

56-
default_arg_repr = "['-a', '-b']"
57-
self.assertEqual(
58-
str(["-a", "-b"]),
59-
default_arg_repr,
60-
"This test is broken (repr does not match), fix me.",
61-
)
62-
6357
argspec = inspection.getfuncprops("fails", fails)
58+
self.assertIsNotNone(argspec)
6459
defaults = argspec.argspec.defaults
65-
self.assertEqual(str(defaults[0]), default_arg_repr)
60+
self.assertEqual(str(defaults[0]), '["-a", "-b"]')
6661

6762
def test_pasekeywordpairs_string(self):
6863
def spam(eggs="foo, bar"):
6964
pass
7065

7166
defaults = inspection.getfuncprops("spam", spam).argspec.defaults
72-
self.assertEqual(repr(defaults[0]), "'foo, bar'")
67+
self.assertEqual(repr(defaults[0]), '"foo, bar"')
7368

7469
def test_parsekeywordpairs_multiple_keywords(self):
7570
def spam(eggs=23, foobar="yay"):
7671
pass
7772

7873
defaults = inspection.getfuncprops("spam", spam).argspec.defaults
7974
self.assertEqual(repr(defaults[0]), "23")
80-
self.assertEqual(repr(defaults[1]), "'yay'")
75+
self.assertEqual(repr(defaults[1]), '"yay"')
8176

8277
def test_pasekeywordpairs_annotation(self):
8378
def spam(eggs: str = "foo, bar"):
8479
pass
8580

8681
defaults = inspection.getfuncprops("spam", spam).argspec.defaults
87-
self.assertEqual(repr(defaults[0]), "'foo, bar'")
82+
self.assertEqual(repr(defaults[0]), '"foo, bar"')
8883

8984
def test_get_encoding_ascii(self):
9085
self.assertEqual(inspection.get_encoding(encoding_ascii), "ascii")
@@ -134,8 +129,15 @@ def test_getfuncprops_print(self):
134129
self.assertIn("file", props.argspec.kwonly)
135130
self.assertIn("flush", props.argspec.kwonly)
136131
self.assertIn("sep", props.argspec.kwonly)
137-
self.assertEqual(props.argspec.kwonly_defaults["file"], "sys.stdout")
138-
self.assertEqual(props.argspec.kwonly_defaults["flush"], "False")
132+
if _is_py311:
133+
self.assertEqual(
134+
repr(props.argspec.kwonly_defaults["file"]), "None"
135+
)
136+
else:
137+
self.assertEqual(
138+
repr(props.argspec.kwonly_defaults["file"]), "sys.stdout"
139+
)
140+
self.assertEqual(repr(props.argspec.kwonly_defaults["flush"]), "False")
139141

140142
@unittest.skipUnless(
141143
numpy is not None and numpy.__version__ >= "1.18",
@@ -173,12 +175,12 @@ def fun_annotations(number: int, lst: List[int] = []) -> List[int]:
173175
props = inspection.getfuncprops("fun", fun)
174176
self.assertEqual(props.func, "fun")
175177
self.assertEqual(props.argspec.args, ["number", "lst"])
176-
self.assertEqual(props.argspec.defaults[0], [])
178+
self.assertEqual(repr(props.argspec.defaults[0]), "[]")
177179

178180
props = inspection.getfuncprops("fun_annotations", fun_annotations)
179181
self.assertEqual(props.func, "fun_annotations")
180182
self.assertEqual(props.argspec.args, ["number", "lst"])
181-
self.assertEqual(props.argspec.defaults[0], [])
183+
self.assertEqual(repr(props.argspec.defaults[0]), "[]")
182184

183185
def test_issue_966_class_method(self):
184186
class Issue966(Sequence):
@@ -215,7 +217,7 @@ def bmethod(cls, number, lst):
215217
)
216218
self.assertEqual(props.func, "cmethod")
217219
self.assertEqual(props.argspec.args, ["number", "lst"])
218-
self.assertEqual(props.argspec.defaults[0], [])
220+
self.assertEqual(repr(props.argspec.defaults[0]), "[]")
219221

220222
def test_issue_966_static_method(self):
221223
class Issue966(Sequence):
@@ -252,7 +254,7 @@ def bmethod(number, lst):
252254
)
253255
self.assertEqual(props.func, "cmethod")
254256
self.assertEqual(props.argspec.args, ["number", "lst"])
255-
self.assertEqual(props.argspec.defaults[0], [])
257+
self.assertEqual(repr(props.argspec.defaults[0]), "[]")
256258

257259

258260
class A:

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy