Skip to content

Commit 3047fe2

Browse files
committed
Move Repl.clean_object and Repl.restore_object into a context manager.
getpydocspec() now uses the __name__ attribute of the function for matching the name found in the docstring.
1 parent 2f8a6fc commit 3047fe2

File tree

1 file changed

+53
-54
lines changed

1 file changed

+53
-54
lines changed

bpython/cli.py

Lines changed: 53 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,54 @@ def writetb(self, l):
252252
fancy."""
253253
map(self.write, ["\x01%s\x03%s" % (OPTS.color_scheme['error'], i) for i in l])
254254

255+
class AttrCleaner(object):
256+
"""A context manager that tries to make an object not exhibit side-effects
257+
on attribute lookup."""
258+
def __init__(self, obj):
259+
self.obj = obj
260+
261+
def __enter__(self):
262+
"""Try to make an object not exhibit side-effects on attribute
263+
lookup."""
264+
type_ = type(self.obj)
265+
__getattribute__ = None
266+
__getattr__ = None
267+
# Dark magic:
268+
# If __getattribute__ doesn't exist on the class and __getattr__ does
269+
# then __getattr__ will be called when doing
270+
# getattr(type_, '__getattribute__', None)
271+
# so we need to first remove the __getattr__, then the
272+
# __getattribute__, then look up the attributes and then restore the
273+
# original methods. :-(
274+
# The upshot being that introspecting on an object to display its
275+
# attributes will avoid unwanted side-effects.
276+
if type_ != types.InstanceType:
277+
__getattr__ = getattr(type_, '__getattr__', None)
278+
if __getattr__ is not None:
279+
try:
280+
setattr(type_, '__getattr__', (lambda _: None))
281+
except TypeError:
282+
__getattr__ = None
283+
__getattribute__ = getattr(type_, '__getattribute__', None)
284+
if __getattribute__ is not None:
285+
try:
286+
setattr(type_, '__getattribute__', object.__getattribute__)
287+
except TypeError:
288+
# XXX: This happens for e.g. built-in types
289+
__getattribute__ = None
290+
self.attribs = (__getattribute__, __getattr__)
291+
# /Dark magic
292+
293+
def __exit__(self, exc_type, exc_val, exc_tb):
294+
"""Restore an object's magic methods."""
295+
type_ = type(self.obj)
296+
__getattribute__, __getattr__ = self.attribs
297+
# Dark magic:
298+
if __getattribute__ is not None:
299+
setattr(type_, '__getattribute__', __getattribute__)
300+
if __getattr__ is not None:
301+
setattr(type_, '__getattr__', __getattr__)
302+
# /Dark magic
255303

256304
class Repl(object):
257305
"""Implements the necessary guff for a Python-repl-alike interface
@@ -344,50 +392,6 @@ def __init__(self, scr, interp, statusbar=None, idle=None):
344392
'ignore') as hfile:
345393
self.rl_hist = hfile.readlines()
346394

347-
def clean_object(self, obj):
348-
"""Try to make an object not exhibit side-effects on attribute
349-
lookup. Return the type's magic attributes so they can be reapplied
350-
with restore_object"""
351-
type_ = type(obj)
352-
__getattribute__ = None
353-
__getattr__ = None
354-
# Dark magic:
355-
# If __getattribute__ doesn't exist on the class and __getattr__ does
356-
# then __getattr__ will be called when doing
357-
# getattr(type_, '__getattribute__', None)
358-
# so we need to first remove the __getattr__, then the
359-
# __getattribute__, then look up the attributes and then restore the
360-
# original methods. :-(
361-
# The upshot being that introspecting on an object to display its
362-
# attributes will avoid unwanted side-effects.
363-
if type_ != types.InstanceType:
364-
__getattr__ = getattr(type_, '__getattr__', None)
365-
if __getattr__ is not None:
366-
try:
367-
setattr(type_, '__getattr__', (lambda _: None))
368-
except TypeError:
369-
__getattr__ = None
370-
__getattribute__ = getattr(type_, '__getattribute__', None)
371-
if __getattribute__ is not None:
372-
try:
373-
setattr(type_, '__getattribute__', object.__getattribute__)
374-
except TypeError:
375-
# XXX: This happens for e.g. built-in types
376-
__getattribute__ = None
377-
# /Dark magic
378-
return __getattribute__, __getattr__
379-
380-
def restore_object(self, obj, attribs):
381-
"""Restore an object's magic methods as returned from clean_object"""
382-
type_ = type(obj)
383-
__getattribute__, __getattr__ = attribs
384-
# Dark magic:
385-
if __getattribute__ is not None:
386-
setattr(type_, '__getattribute__', __getattribute__)
387-
if __getattr__ is not None:
388-
setattr(type_, '__getattr__', __getattr__)
389-
# /Dark magic
390-
391395
def attr_matches(self, text):
392396
"""Taken from rlcompleter.py and bent to my will."""
393397

@@ -397,11 +401,8 @@ def attr_matches(self, text):
397401

398402
expr, attr = m.group(1, 3)
399403
obj = eval(expr, self.interp.locals)
400-
attribs = self.clean_object(obj)
401-
try:
404+
with AttrCleaner(obj):
402405
matches = self.attr_lookup(obj, expr, attr)
403-
finally:
404-
self.restore_object(obj, attribs)
405406
return matches
406407

407408
def attr_lookup(self, obj, expr, attr):
@@ -422,12 +423,9 @@ def attr_lookup(self, obj, expr, attr):
422423

423424
def _callable_postfix(self, value, word):
424425
"""rlcompleter's _callable_postfix done right."""
425-
attribs = self.clean_object(value)
426-
try:
426+
with AttrCleaner(value):
427427
if hasattr(value, '__call__'):
428428
word += '('
429-
finally:
430-
self.restore_object(value, attribs)
431429
return word
432430

433431
def cw(self):
@@ -469,7 +467,7 @@ def getpydocspec(f, func):
469467
if s is None:
470468
return None
471469

472-
if s.groups()[0] != func:
470+
if not hasattr(f, '__name__') or s.groups()[0] != f.__name__:
473471
return None
474472

475473
args = [i.strip() for i in s.groups()[1].split(',')]
@@ -500,7 +498,8 @@ def getargspec(func):
500498
return True
501499

502500
except (NameError, TypeError, KeyError):
503-
t = getpydocspec(f, func)
501+
with AttrCleaner(f):
502+
t = getpydocspec(f, func)
504503
if t is None:
505504
return None
506505
self.argspec = t

0 commit comments

Comments
 (0)