Skip to content

Commit 234b8ba

Browse files
committed
re-add AttrCleaner for one spot and add testing
1 parent d410c8c commit 234b8ba

File tree

4 files changed

+146
-133
lines changed

4 files changed

+146
-133
lines changed

bpython/autocomplete.py

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -357,14 +357,13 @@ def attr_matches(self, text, namespace):
357357
return matches
358358

359359
def attr_lookup(self, obj, expr, attr):
360-
"""Second half of original attr_matches method factored out so it can
361-
be wrapped in a safe try/finally block in case anything bad happens to
362-
restore the original __getattribute__ method."""
360+
"""Second half of attr_matches."""
363361
words = self.list_attributes(obj)
364362
if inspection.hasattr_safe(obj, "__class__"):
365363
words.append("__class__")
366-
words = words + rlcompleter.get_class_members(obj.__class__)
367-
if not isinstance(obj.__class__, abc.ABCMeta):
364+
klass = inspection.getattr_safe(obj, "__class__")
365+
words = words + rlcompleter.get_class_members(klass)
366+
if not isinstance(klass, abc.ABCMeta):
368367
try:
369368
words.remove("__abstractmethods__")
370369
except ValueError:
@@ -384,21 +383,25 @@ def attr_lookup(self, obj, expr, attr):
384383
if py3:
385384

386385
def list_attributes(self, obj):
387-
return dir(obj)
386+
# TODO: re-implement dir using getattr_static to avoid using
387+
# AttrCleaner here?
388+
with inspection.AttrCleaner(obj):
389+
return dir(obj)
388390

389391
else:
390392

391393
def list_attributes(self, obj):
392-
if isinstance(obj, InstanceType):
393-
try:
394+
with inspection.AttrCleaner(obj):
395+
if isinstance(obj, InstanceType):
396+
try:
397+
return dir(obj)
398+
except Exception:
399+
# This is a case where we can not prevent user code from
400+
# running. We return a default list attributes on error
401+
# instead. (#536)
402+
return ["__doc__", "__module__"]
403+
else:
394404
return dir(obj)
395-
except Exception:
396-
# This is a case where we can not prevent user code from
397-
# running. We return a default list attributes on error
398-
# instead. (#536)
399-
return ["__doc__", "__module__"]
400-
else:
401-
return dir(obj)
402405

403406

404407
class DictKeyCompletion(BaseCompletionType):

bpython/test/test_autocomplete.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,9 @@ def asserts_when_called(self):
251251
class Slots(object):
252252
__slots__ = ["a", "b"]
253253

254+
class OverriddenGetattribute(Foo):
255+
def __getattribute__(self,name):
256+
raise AssertionError("custom get attribute invoked")
254257

255258
class TestAttrCompletion(unittest.TestCase):
256259
@classmethod
@@ -298,12 +301,20 @@ def test_descriptor_attributes_not_run(self):
298301
com.matches(2, "a.", locals_={"a": Properties()}),
299302
set(["a.b", "a.a", "a.method", "a.asserts_when_called"]),
300303
)
304+
305+
def test_custom_get_attribute_not_invoked(self):
306+
com = autocomplete.AttrCompletion()
307+
self.assertSetEqual(
308+
com.matches(2, "a.", locals_={"a": OverriddenGetattribute()}),
309+
set(["a.b", "a.a", "a.method"]),
310+
)
311+
301312

302313
def test_slots_not_crash(self):
303314
com = autocomplete.AttrCompletion()
304315
self.assertSetEqual(
305316
com.matches(2, "A.", locals_={"A": Slots}),
306-
set(["A.b", "A.a", "A.mro"]),
317+
set(["A.b", "A.a"]),
307318
)
308319

309320

bpython/test/test_inspection.py

Lines changed: 116 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -155,73 +155,124 @@ def test_get_source_file(self):
155155
self.assertEqual(encoding, "utf-8")
156156

157157

158-
class TestSafeGetAttribute(unittest.TestCase):
159-
def test_lookup_on_object(self):
160-
a = A()
161-
a.x = 1
162-
self.assertEqual(getattr_safe(a, "x"), 1)
163-
self.assertEqual(getattr_safe(a, "a"), "a")
164-
b = B()
165-
b.y = 2
166-
self.assertEqual(getattr_safe(b, "y"), 2)
167-
self.assertEqual(getattr_safe(b, "a"), "a")
168-
self.assertEqual(getattr_safe(b, "b"), "b")
169-
170-
self.assertEqual(hasattr_safe(b, "y"), True)
171-
self.assertEqual(hasattr_safe(b, "b"), True)
172-
173-
174-
def test_avoid_running_properties(self):
175-
p = Property()
176-
self.assertEqual(getattr_safe(p, "prop"), Property.prop)
177-
self.assertEqual(hasattr_safe(p, "prop"), True)
178-
179-
def test_lookup_with_slots(self):
180-
s = Slots()
181-
s.s1 = "s1"
182-
self.assertEqual(getattr_safe(s, "s1"), "s1")
183-
with self.assertRaises(AttributeError):
184-
getattr_safe(s, "s2")
185-
186-
self.assertEqual(hasattr_safe(s, "s2"), False)
187-
188-
def test_lookup_on_slots_classes(self):
189-
sga = getattr_safe
190-
s = SlotsSubclass()
191-
self.assertIsInstance(sga(Slots, "s1"), member_descriptor)
192-
self.assertIsInstance(sga(SlotsSubclass, "s1"), member_descriptor)
193-
self.assertIsInstance(sga(SlotsSubclass, "s4"), property)
194-
self.assertIsInstance(sga(s, "s4"), property)
195-
196-
self.assertEqual(hasattr_safe(s, "s1"), True)
197-
self.assertEqual(hasattr_safe(s, "s4"), True)
198-
199-
@unittest.skipIf(py3, "Py 3 doesn't allow slots and prop in same class")
200-
def test_lookup_with_property_and_slots(self):
201-
sga = getattr_safe
202-
s = SlotsSubclass()
203-
self.assertIsInstance(sga(Slots, "s3"), property)
204-
self.assertEqual(getattr_safe(s, "s3"), Slots.__dict__["s3"])
205-
self.assertIsInstance(sga(SlotsSubclass, "s3"), property)
206-
207-
def test_lookup_on_overridden_methods(self):
208-
sga = getattr_safe
209-
self.assertEqual(sga(OverriddenGetattr(), "a"), 1)
210-
self.assertEqual(sga(OverriddenGetattribute(), "a"), 1)
211-
self.assertEqual(sga(OverriddenMRO(), "a"), 1)
212-
with self.assertRaises(AttributeError):
213-
sga(OverriddenGetattr(), "b")
214-
with self.assertRaises(AttributeError):
215-
sga(OverriddenGetattribute(), "b")
216-
with self.assertRaises(AttributeError):
217-
sga(OverriddenMRO(), "b")
218-
219-
self.assertEqual(hasattr_safe(OverriddenGetattr(), "b"), False)
220-
self.assertEqual(hasattr_safe(OverriddenGetattribute(), "b"), False)
221-
self.assertEqual(hasattr_safe(OverriddenMRO(), "b"), False)
222-
158+
class A(object):
159+
a = "a"
160+
161+
162+
class B(A):
163+
b = "b"
164+
165+
166+
class Property(object):
167+
@property
168+
def prop(self):
169+
raise AssertionError("Property __get__ executed")
170+
171+
172+
class Slots(object):
173+
__slots__ = ["s1", "s2", "s3"]
174+
175+
if not py3:
176+
177+
@property
178+
def s3(self):
179+
raise AssertionError("Property __get__ executed")
180+
181+
182+
class SlotsSubclass(Slots):
183+
@property
184+
def s4(self):
185+
raise AssertionError("Property __get__ executed")
186+
187+
188+
class OverriddenGetattr(object):
189+
def __getattr__(self, attr):
190+
raise AssertionError("custom __getattr__ executed")
191+
192+
a = 1
223193

224194

195+
class OverriddenGetattribute(object):
196+
def __getattribute__(self, attr):
197+
raise AssertionError("custom __getattribute__ executed")
198+
199+
a = 1
200+
201+
202+
class OverriddenMRO(object):
203+
def __mro__(self):
204+
raise AssertionError("custom mro executed")
205+
206+
a = 1
207+
208+
member_descriptor = type(Slots.s1)
209+
210+
class TestSafeGetAttribute(unittest.TestCase):
211+
def test_lookup_on_object(self):
212+
a = A()
213+
a.x = 1
214+
self.assertEqual(inspection.getattr_safe(a, "x"), 1)
215+
self.assertEqual(inspection.getattr_safe(a, "a"), "a")
216+
b = B()
217+
b.y = 2
218+
self.assertEqual(inspection.getattr_safe(b, "y"), 2)
219+
self.assertEqual(inspection.getattr_safe(b, "a"), "a")
220+
self.assertEqual(inspection.getattr_safe(b, "b"), "b")
221+
222+
self.assertEqual(inspection.hasattr_safe(b, "y"), True)
223+
self.assertEqual(inspection.hasattr_safe(b, "b"), True)
224+
225+
226+
def test_avoid_running_properties(self):
227+
p = Property()
228+
self.assertEqual(inspection.getattr_safe(p, "prop"), Property.prop)
229+
self.assertEqual(inspection.hasattr_safe(p, "prop"), True)
230+
231+
def test_lookup_with_slots(self):
232+
s = Slots()
233+
s.s1 = "s1"
234+
self.assertEqual(inspection.getattr_safe(s, "s1"), "s1")
235+
with self.assertRaises(AttributeError):
236+
inspection.getattr_safe(s, "s2")
237+
238+
self.assertEqual(inspection.hasattr_safe(s, "s1"), True)
239+
self.assertEqual(inspection.hasattr_safe(s, "s2"), False)
240+
241+
def test_lookup_on_slots_classes(self):
242+
sga = inspection.getattr_safe
243+
s = SlotsSubclass()
244+
self.assertIsInstance(sga(Slots, "s1"), member_descriptor)
245+
self.assertIsInstance(sga(SlotsSubclass, "s1"), member_descriptor)
246+
self.assertIsInstance(sga(SlotsSubclass, "s4"), property)
247+
self.assertIsInstance(sga(s, "s4"), property)
248+
249+
self.assertEqual(inspection.hasattr_safe(s, "s1"), False)
250+
self.assertEqual(inspection.hasattr_safe(s, "s4"), True)
251+
252+
@unittest.skipIf(py3, "Py 3 doesn't allow slots and prop in same class")
253+
def test_lookup_with_property_and_slots(self):
254+
sga = inspection.getattr_safe
255+
s = SlotsSubclass()
256+
self.assertIsInstance(sga(Slots, "s3"), property)
257+
self.assertEqual(inspection.getattr_safe(s, "s3"), Slots.__dict__["s3"])
258+
self.assertIsInstance(sga(SlotsSubclass, "s3"), property)
259+
260+
def test_lookup_on_overridden_methods(self):
261+
sga = inspection.getattr_safe
262+
self.assertEqual(sga(OverriddenGetattr(), "a"), 1)
263+
self.assertEqual(sga(OverriddenGetattribute(), "a"), 1)
264+
self.assertEqual(sga(OverriddenMRO(), "a"), 1)
265+
with self.assertRaises(AttributeError):
266+
sga(OverriddenGetattr(), "b")
267+
with self.assertRaises(AttributeError):
268+
sga(OverriddenGetattribute(), "b")
269+
with self.assertRaises(AttributeError):
270+
sga(OverriddenMRO(), "b")
271+
272+
self.assertEqual(inspection.hasattr_safe(OverriddenGetattr(), "b"), False)
273+
self.assertEqual(inspection.hasattr_safe(OverriddenGetattribute(), "b"), False)
274+
self.assertEqual(inspection.hasattr_safe(OverriddenMRO(), "b"), False)
275+
225276

226277
if __name__ == "__main__":
227278
unittest.main()

bpython/test/test_simpleeval.py

Lines changed: 0 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -133,58 +133,6 @@ def test_with_namespace(self):
133133
self.assertCannotEval("a[1].a|bc", {})
134134

135135

136-
class A(object):
137-
a = "a"
138-
139-
140-
class B(A):
141-
b = "b"
142-
143-
144-
class Property(object):
145-
@property
146-
def prop(self):
147-
raise AssertionError("Property __get__ executed")
148-
149-
150-
class Slots(object):
151-
__slots__ = ["s1", "s2", "s3"]
152-
153-
if not py3:
154-
155-
@property
156-
def s3(self):
157-
raise AssertionError("Property __get__ executed")
158-
159-
160-
class SlotsSubclass(Slots):
161-
@property
162-
def s4(self):
163-
raise AssertionError("Property __get__ executed")
164-
165-
166-
class OverriddenGetattr(object):
167-
def __getattr__(self, attr):
168-
raise AssertionError("custom __getattr__ executed")
169-
170-
a = 1
171-
172-
173-
class OverriddenGetattribute(object):
174-
def __getattribute__(self, attr):
175-
raise AssertionError("custom __getattribute__ executed")
176-
177-
a = 1
178-
179-
180-
class OverriddenMRO(object):
181-
def __mro__(self):
182-
raise AssertionError("custom mro executed")
183-
184-
a = 1
185-
186-
187-
member_descriptor = type(Slots.s1)
188136

189137

190138
if __name__ == "__main__":

0 commit comments

Comments
 (0)