Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Merge pull request #1529 from drtimcouper/master

Implement new configurable flag to control tab completion on modules that implement the __all__ attribute:

IPCompleter.limit_to__all__= Boolean.
Instruct the completer to use __all__ for the completion
Specifically, when completing on ``object.<tab>``.
When True: only those names in obj.__all__ will be included.
When False [default]: the __all__ attribute is ignored.
  • Loading branch information...
commit 47367a0630931ecd4fa453caec22acb1b887c8dd 2 parents e0b4311 + d0e7b4d
@fperez fperez authored
View
31 IPython/core/completer.py
@@ -336,7 +336,7 @@ def attr_matches(self, text):
#io.rprint('Completer->attr_matches, txt=%r' % text) # dbg
# Another option, seems to work great. Catches things like ''.<tab>
m = re.match(r"(\S+(\.\w+)*)\.(\w*)$", text)
-
+
if m:
expr, attr = m.group(1, 3)
elif self.greedy:
@@ -346,7 +346,7 @@ def attr_matches(self, text):
expr, attr = m2.group(1,2)
else:
return []
-
+
try:
obj = eval(expr, self.namespace)
except:
@@ -355,7 +355,10 @@ def attr_matches(self, text):
except:
return []
- words = dir2(obj)
+ if self.limit_to__all__ and hasattr(obj, '__all__'):
+ words = get__all__entries(obj)
+ else:
+ words = dir2(obj)
try:
words = generics.complete_object(obj, words)
@@ -371,6 +374,16 @@ def attr_matches(self, text):
return res
+def get__all__entries(obj):
+ """returns the strings in the __all__ attribute"""
+ try:
+ words = getattr(obj,'__all__')
+ except:
+ return []
+
+ return [w for w in words if isinstance(w, basestring)]
+
+
class IPCompleter(Completer):
"""Extension of the completer class with IPython-specific features"""
@@ -403,6 +416,16 @@ def _greedy_changed(self, name, old, new):
When 0: nothing will be excluded.
"""
)
+ limit_to__all__ = CBool(default_value=False, config=True,
+ help="""Instruct the completer to use __all__ for the completion
+
+ Specifically, when completing on ``object.<tab>``.
+
+ When True: only those names in obj.__all__ will be included.
+
+ When False [default]: the __all__ attribute is ignored
+ """
+ )
def __init__(self, shell=None, namespace=None, global_namespace=None,
alias_table=None, use_readline=True,
@@ -602,7 +625,7 @@ def alias_matches(self, text):
def python_matches(self,text):
"""Match attributes or global python names"""
-
+
#io.rprint('Completer->python_matches, txt=%r' % text) # dbg
if "." in text:
try:
View
6 IPython/core/magic.py
@@ -3731,6 +3731,12 @@ def magic_config(self, s):
Whether to merge completion results into a single list
If False, only the completion results from the first non-empty completer
will be returned.
+ IPCompleter.limit_to__all__=<CBool>
+ Current: False
+ Instruct the completer to use __all__ for the completion
+ Specifically, when completing on ``object.<tab>``.
+ When True: only those names in obj.__all__ will be included.
+ When False [default]: the __all__ attribute is ignored
IPCompleter.greedy=<CBool>
Current: False
Activate greedy completion
View
38 IPython/core/tests/test_completer.py
@@ -229,4 +229,40 @@ def test_omit__names():
nt.assert_false('ip.__str__' in matches)
nt.assert_false('ip._hidden_attr' in matches)
del ip._hidden_attr
-
+
+
+def test_limit_to__all__False_ok():
+ ip = get_ipython()
+ c = ip.Completer
+ ip.ex('class D: x=24')
+ ip.ex('d=D()')
+ cfg = Config()
+ cfg.IPCompleter.limit_to__all__ = False
+ c.update_config(cfg)
+ s, matches = c.complete('d.')
+ nt.assert_true('d.x' in matches)
+
+def test_limit_to__all__True_ok():
+ ip = get_ipython()
+ c = ip.Completer
+ ip.ex('class D: x=24')
+ ip.ex('d=D()')
+ ip.ex("d.__all__=['z']")
+ cfg = Config()
+ cfg.IPCompleter.limit_to__all__ = True
+ c.update_config(cfg)
+ s, matches = c.complete('d.')
+ nt.assert_true('d.z' in matches)
+ nt.assert_false('d.x' in matches)
+
+def test_get__all__entries_ok():
+ class A(object):
+ __all__ = ['x', 1]
+ words = completer.get__all__entries(A())
+ nt.assert_equal(words, ['x'])
+
+def test_get__all__entries_no__all__ok():
+ class A(object):
+ pass
+ words = completer.get__all__entries(A())
+ nt.assert_equal(words, [])
View
57 IPython/utils/dir2.py
@@ -19,7 +19,7 @@
def get_class_members(cls):
ret = dir(cls)
- if hasattr(cls,'__bases__'):
+ if hasattr(cls, '__bases__'):
try:
bases = cls.__bases__
except AttributeError:
@@ -46,49 +46,28 @@ def dir2(obj):
# Start building the attribute list via dir(), and then complete it
# with a few extra special-purpose calls.
- words = dir(obj)
- if hasattr(obj,'__class__'):
- words.append('__class__')
- words.extend(get_class_members(obj.__class__))
- #if '__base__' in words: 1/0
+ words = set(dir(obj))
- # Some libraries (such as traits) may introduce duplicates, we want to
- # track and clean this up if it happens
- may_have_dupes = False
+ if hasattr(obj, '__class__'):
+ #words.add('__class__')
+ words |= set(get_class_members(obj.__class__))
- # this is the 'dir' function for objects with Enthought's traits
- if hasattr(obj, 'trait_names'):
- try:
- words.extend(obj.trait_names())
- may_have_dupes = True
- except TypeError:
- # This will happen if `obj` is a class and not an instance.
- pass
- except AttributeError:
- # `obj` lied to hasatter (e.g. Pyro), ignore
- pass
-
- # Support for PyCrust-style _getAttributeNames magic method.
- if hasattr(obj, '_getAttributeNames'):
- try:
- words.extend(obj._getAttributeNames())
- may_have_dupes = True
- except TypeError:
- # `obj` is a class and not an instance. Ignore
- # this error.
- pass
- except AttributeError:
- # `obj` lied to hasatter (e.g. Pyro), ignore
- pass
- if may_have_dupes:
- # eliminate possible duplicates, as some traits may also
- # appear as normal attributes in the dir() call.
- words = list(set(words))
- words.sort()
+ # for objects with Enthought's traits, add trait_names() list
+ # for PyCrust-style, add _getAttributeNames() magic method list
+ for attr in ('trait_names', '_getAttributeNames'):
+ if hasattr(obj, attr):
+ try:
+ func = getattr(obj, attr)
+ if callable(func):
+ words |= set(func())
+ except:
+ # TypeError: obj is class not instance
+ pass
# filter out non-string attributes which may be stuffed by dir() calls
# and poor coding in third-party modules
- return [w for w in words if isinstance(w, basestring)]
+ words = [w for w in words if isinstance(w, basestring)]
+ return sorted(words)
View
52 IPython/utils/tests/test_dir2.py
@@ -0,0 +1,52 @@
+import nose.tools as nt
+from IPython.utils.dir2 import dir2
+
+
+class Base(object):
+ x = 1
+ z = 23
+
+
+def test_base():
+ res = dir2(Base())
+ assert ('x' in res)
+ assert ('z' in res)
+ assert ('y' not in res)
+ assert ('__class__' in res)
+ nt.assert_equal(res.count('x'), 1)
+ nt.assert_equal(res.count('__class__'), 1)
+
+def test_SubClass():
+
+ class SubClass(Base):
+ y = 2
+
+ res = dir2(SubClass())
+ assert ('y' in res)
+ nt.assert_equal(res.count('y'), 1)
+ nt.assert_equal(res.count('x'), 1)
+
+
+def test_SubClass_with_trait_names_method():
+
+ class SubClass(Base):
+ y = 2
+ def trait_names(self):
+ return ['t', 'umbrella']
+
+ res = dir2(SubClass())
+ assert('trait_names' in res)
+ assert('umbrella' in res)
+ nt.assert_equal(res[-6:], ['t', 'trait_names','umbrella', 'x','y','z'])
+ nt.assert_equal(res.count('t'), 1)
+
+
+def test_SubClass_with_trait_names_attr():
+ # usecase: trait_names is used in a class describing psychological classification
+
+ class SubClass(Base):
+ y = 2
+ trait_names = 44
+
+ res = dir2(SubClass())
+ assert('trait_names' in res)
Please sign in to comment.
Something went wrong with that request. Please try again.