Update rlcompleter from 3.13.5 (#5990)

This commit is contained in:
Shahar Naveh authored and GitHub committed 2025-07-17 13:44:18 +09:00
1 parent 559a7a56e5
commit 80a9e0ed54
2 files changed
+90 -26

No files matched your search

+31 -15
View File
@@ -31,7 +31,11 @@ Notes:
import atexit
import builtins
import inspect
import keyword
import re
import __main__
import warnings
__all__ = ["Completer"]
@@ -85,10 +89,11 @@ class Completer:
return None
if state == 0:
if "." in text:
self.matches = self.attr_matches(text)
else:
self.matches = self.global_matches(text)
with warnings.catch_warnings(action="ignore"):
if "." in text:
self.matches = self.attr_matches(text)
else:
self.matches = self.global_matches(text)
try:
return self.matches[state]
except IndexError:
@@ -96,7 +101,13 @@ class Completer:
def _callable_postfix(self, val, word):
if callable(val):
word = word + "("
word += "("
try:
if not inspect.signature(val).parameters:
word += ")"
except ValueError:
pass
return word
def global_matches(self, text):
@@ -106,18 +117,17 @@ class Completer:
defined in self.namespace that match.
"""
import keyword
matches = []
seen = {"__builtins__"}
n = len(text)
for word in keyword.kwlist:
for word in keyword.kwlist + keyword.softkwlist:
if word[:n] == text:
seen.add(word)
if word in {'finally', 'try'}:
word = word + ':'
elif word not in {'False', 'None', 'True',
'break', 'continue', 'pass',
'else'}:
'else', '_'}:
word = word + ' '
matches.append(word)
for nspace in [self.namespace, builtins.__dict__]:
@@ -139,7 +149,6 @@ class Completer:
with a __getattr__ hook is evaluated.
"""
import re
m = re.match(r"(\w+(\.\w+)*)\.(\w*)", text)
if not m:
return []
@@ -169,13 +178,20 @@ class Completer:
if (word[:n] == attr and
not (noprefix and word[:n+1] == noprefix)):
match = "%s.%s" % (expr, word)
try:
val = getattr(thisobject, word)
except Exception:
pass # Include even if attribute not set
if isinstance(getattr(type(thisobject), word, None),
property):
# bpo-44752: thisobject.word is a method decorated by
# `@property`. What follows applies a postfix if
# thisobject.word is callable, but know we know that
# this is not callable (because it is a property).
# Also, getattr(thisobject, word) will evaluate the
# property method, which is not desirable.
matches.append(match)
continue
if (value := getattr(thisobject, word, None)) is not None:
matches.append(self._callable_postfix(value, match))
else:
match = self._callable_postfix(val, match)
matches.append(match)
matches.append(match)
if matches or not noprefix:
break
if noprefix == '_':
+59 -11
View File
@@ -2,6 +2,7 @@ import unittest
from unittest.mock import patch
import builtins
import rlcompleter
from test.support import MISSING_C_DOCSTRINGS
class CompleteMe:
""" Trivial class used in testing rlcompleter.Completer. """
@@ -40,21 +41,41 @@ class TestRlcompleter(unittest.TestCase):
# test with a customized namespace
self.assertEqual(self.completer.global_matches('CompleteM'),
['CompleteMe('])
['CompleteMe(' if MISSING_C_DOCSTRINGS else 'CompleteMe()'])
self.assertEqual(self.completer.global_matches('eg'),
['egg('])
# XXX: see issue5256
self.assertEqual(self.completer.global_matches('CompleteM'),
['CompleteMe('])
['CompleteMe(' if MISSING_C_DOCSTRINGS else 'CompleteMe()'])
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_attr_matches(self):
# test with builtins namespace
self.assertEqual(self.stdcompleter.attr_matches('str.s'),
['str.{}('.format(x) for x in dir(str)
if x.startswith('s')])
self.assertEqual(self.stdcompleter.attr_matches('tuple.foospamegg'), [])
expected = sorted({'None.%s%s' % (x, '(' if x != '__doc__' else '')
for x in dir(None)})
def create_expected_for_none():
if not MISSING_C_DOCSTRINGS:
parentheses = ('__init_subclass__', '__class__')
else:
# When `--without-doc-strings` is used, `__class__`
# won't have a known signature.
parentheses = ('__init_subclass__',)
items = set()
for x in dir(None):
if x in parentheses:
items.add(f'None.{x}()')
elif x == '__doc__':
items.add(f'None.{x}')
else:
items.add(f'None.{x}(')
return sorted(items)
expected = create_expected_for_none()
self.assertEqual(self.stdcompleter.attr_matches('None.'), expected)
self.assertEqual(self.stdcompleter.attr_matches('None._'), expected)
self.assertEqual(self.stdcompleter.attr_matches('None.__'), expected)
@@ -64,7 +85,7 @@ class TestRlcompleter(unittest.TestCase):
['CompleteMe.spam'])
self.assertEqual(self.completer.attr_matches('Completeme.egg'), [])
self.assertEqual(self.completer.attr_matches('CompleteMe.'),
['CompleteMe.mro(', 'CompleteMe.spam'])
['CompleteMe.mro()', 'CompleteMe.spam'])
self.assertEqual(self.completer.attr_matches('CompleteMe._'),
['CompleteMe._ham'])
matches = self.completer.attr_matches('CompleteMe.__')
@@ -81,18 +102,42 @@ class TestRlcompleter(unittest.TestCase):
if x.startswith('s')])
def test_excessive_getattr(self):
# Ensure getattr() is invoked no more than once per attribute
"""Ensure getattr() is invoked no more than once per attribute"""
# note the special case for @property methods below; that is why
# we use __dir__ and __getattr__ in class Foo to create a "magic"
# class attribute 'bar'. This forces `getattr` to call __getattr__
# (which is doesn't necessarily do).
class Foo:
calls = 0
@property
def bar(self):
self.calls += 1
return None
bar = ''
def __getattribute__(self, name):
if name == 'bar':
self.calls += 1
return None
return super().__getattribute__(name)
f = Foo()
completer = rlcompleter.Completer(dict(f=f))
self.assertEqual(completer.complete('f.b', 0), 'f.bar')
self.assertEqual(f.calls, 1)
def test_property_method_not_called(self):
class Foo:
_bar = 0
property_called = False
@property
def bar(self):
self.property_called = True
return self._bar
f = Foo()
completer = rlcompleter.Completer(dict(f=f))
self.assertEqual(completer.complete('f.b', 0), 'f.bar')
self.assertFalse(f.property_called)
def test_uncreated_attr(self):
# Attributes like properties and slots should be completed even when
# they haven't been created on an instance
@@ -114,6 +159,9 @@ class TestRlcompleter(unittest.TestCase):
self.assertEqual(completer.complete('el', 0), 'elif ')
self.assertEqual(completer.complete('el', 1), 'else')
self.assertEqual(completer.complete('tr', 0), 'try:')
self.assertEqual(completer.complete('_', 0), '_')
self.assertEqual(completer.complete('match', 0), 'match ')
self.assertEqual(completer.complete('case', 0), 'case ')
def test_duplicate_globals(self):
namespace = {
@@ -134,7 +182,7 @@ class TestRlcompleter(unittest.TestCase):
# No opening bracket "(" because we overrode the built-in class
self.assertEqual(completer.complete('memoryview', 0), 'memoryview')
self.assertIsNone(completer.complete('memoryview', 1))
self.assertEqual(completer.complete('Ellipsis', 0), 'Ellipsis(')
self.assertEqual(completer.complete('Ellipsis', 0), 'Ellipsis()')
self.assertIsNone(completer.complete('Ellipsis', 1))
if __name__ == '__main__':