Skip to content

Commit 6b1d291

Browse files
test more simple_eval functionality
1 parent c86f222 commit 6b1d291

File tree

4 files changed

+52
-23
lines changed

4 files changed

+52
-23
lines changed

bpython/autocomplete.py

Lines changed: 21 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,22 @@
7474
def after_last_dot(name):
7575
return name.rstrip('.').rsplit('.')[-1]
7676

77+
def few_enough_underscores(current, match):
78+
"""Returns whether match should be shown based on current
79+
80+
if current is _, True if match starts with 0 or 1 underscore
81+
if current is __, True regardless of match
82+
otherwise True if match does not start with any underscore
83+
"""
84+
if current.startswith('__'):
85+
return True
86+
elif current.startswith('_') and not match.startswith('__'):
87+
return True
88+
elif match.startswith('_'):
89+
return False
90+
else:
91+
return True
92+
7793

7894
def method_match_simple(word, size, text):
7995
return word[:size] == text
@@ -255,18 +271,9 @@ def matches(self, cursor_offset, line, **kwargs):
255271
matches = set(''.join([r.word[:-i], m])
256272
for m in self.attr_matches(methodtext, locals_))
257273

258-
# TODO add open paren for methods via _callable_prefix (or decide not
259-
# to) unless the first character is a _ filter out all attributes
260-
# starting with a _
261-
if r.word.split('.')[-1].startswith('__'):
262-
pass
263-
elif r.word.split('.')[-1].startswith('_'):
264-
matches = set(match for match in matches
265-
if not match.split('.')[-1].startswith('__'))
266-
else:
267-
matches = set(match for match in matches
268-
if not match.split('.')[-1].startswith('_'))
269-
return matches
274+
return set(m for m in matches
275+
if few_enough_underscores(r.word.split('.')[-1],
276+
m.split('.')[-1]))
270277

271278
def locate(self, current_offset, line):
272279
return lineparts.current_dotted_attribute(current_offset, line)
@@ -470,14 +477,8 @@ def matches(self, cursor_offset, line, **kwargs):
470477
# strips leading dot
471478
matches = [m[1:] for m in self.attr_lookup(obj, '', attr.word)]
472479

473-
if attr.word.startswith('__'):
474-
pass
475-
elif attr.word.startswith('_'):
476-
matches = set(match for match in matches
477-
if not match.startswith('__'))
478-
else:
479-
matches = set(match for match in matches
480-
if not match.split('.')[-1].startswith('_'))
480+
481+
return set(m for m in matches if few_enough_underscores(attr.word, m))
481482
return matches
482483

483484

bpython/simpleeval.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -168,10 +168,12 @@ def evaluate_current_expression(cursor_offset, line, namespace=None):
168168
"""
169169
Return evaluated expression to the right of the dot of current attribute.
170170
171-
build asts from with increasing numbers of characters.
172-
Find the biggest valid ast.
173-
Once our attribute access is a subtree, stop
171+
Only evaluates builtin objects, and do any attribute lookup.
174172
"""
173+
# Builds asts from with increasing numbers of characters back from cursor.
174+
# Find the biggest valid ast.
175+
# Once our attribute access is found, return its .value subtree
176+
175177
if namespace is None:
176178
namespace = {}
177179

@@ -207,6 +209,7 @@ def parse_trees(cursor_offset, line):
207209

208210

209211
def evaluate_current_attribute(cursor_offset, line, namespace=None):
212+
"""Safely evaluates the expression attribute lookup currently occuring on"""
210213
# this function runs user code in case of custom descriptors,
211214
# so could fail in any way
212215

bpython/test/test_repl.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,13 @@ def test_issue583(self):
230230
self.repl.set_docstring()
231231
self.assertIsNot(self.repl.docstring, None)
232232

233+
def test_methods_of_expressions(self):
234+
self.set_input_line("'a'.capitalize(")
235+
self.assertTrue(self.repl.get_args())
236+
237+
self.set_input_line("(1 + 1).bit_length(")
238+
self.assertTrue(self.repl.get_args())
239+
233240

234241
class TestGetSource(unittest.TestCase):
235242
def setUp(self):

bpython/test/test_simpleeval.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# -*- coding: utf-8 -*-
22

33
import ast
4+
import numbers
45

56
from bpython.simpleeval import (simple_eval,
67
evaluate_current_expression,
@@ -60,6 +61,23 @@ def __getattr__(inner_self, attr):
6061
with self.assertRaises(ValueError):
6162
simple_eval('a[1]', {'a': SchrodingersDict()})
6263

64+
def test_operators_on_suspicious_types(self):
65+
class Spam(numbers.Number):
66+
def __add__(inner_self, other):
67+
self.fail("doing attribute lookup might have side effects")
68+
69+
with self.assertRaises(ValueError):
70+
simple_eval('a + 1', {'a': Spam()})
71+
72+
def test_operators_on_numbers(self):
73+
self.assertEqual(simple_eval('-2'), -2)
74+
self.assertEqual(simple_eval('1 + 1'), 2)
75+
self.assertEqual(simple_eval('a - 2', {'a':1}), -1)
76+
with self.assertRaises(ValueError):
77+
simple_eval('2 * 3')
78+
with self.assertRaises(ValueError):
79+
simple_eval('2 ** 3')
80+
6381
def test_function_calls_raise(self):
6482
with self.assertRaises(ValueError):
6583
simple_eval('1()')

0 commit comments

Comments
 (0)