##// END OF EJS Templates
You Want It Darker
krassowski -
Show More
@@ -923,8 +923,8 b' class Completer(Configurable):'
923 ).tag(config=True)
923 ).tag(config=True)
924
924
925 evaluation = Enum(
925 evaluation = Enum(
926 ('forbidden', 'minimal', 'limitted', 'unsafe', 'dangerous'),
926 ("forbidden", "minimal", "limitted", "unsafe", "dangerous"),
927 default_value='limitted',
927 default_value="limitted",
928 help="""Code evaluation under completion.
928 help="""Code evaluation under completion.
929
929
930 Successive options allow to enable more eager evaluation for more accurate completion suggestions,
930 Successive options allow to enable more eager evaluation for more accurate completion suggestions,
@@ -961,8 +961,7 b' class Completer(Configurable):'
961 "unicode characters back to latex commands.").tag(config=True)
961 "unicode characters back to latex commands.").tag(config=True)
962
962
963 auto_close_dict_keys = Bool(
963 auto_close_dict_keys = Bool(
964 False,
964 False, help="""Enable auto-closing dictionary keys."""
965 help="""Enable auto-closing dictionary keys."""
966 ).tag(config=True)
965 ).tag(config=True)
967
966
968 def __init__(self, namespace=None, global_namespace=None, **kwargs):
967 def __init__(self, namespace=None, global_namespace=None, **kwargs):
@@ -1066,7 +1065,7 b' class Completer(Configurable):'
1066 m2 = re.match(r"(.+)\.(\w*)$", self.line_buffer)
1065 m2 = re.match(r"(.+)\.(\w*)$", self.line_buffer)
1067 if not m2:
1066 if not m2:
1068 return []
1067 return []
1069 expr, attr = m2.group(1,2)
1068 expr, attr = m2.group(1, 2)
1070
1069
1071 obj = self._evaluate_expr(expr)
1070 obj = self._evaluate_expr(expr)
1072
1071
@@ -1090,8 +1089,7 b' class Completer(Configurable):'
1090 pass
1089 pass
1091 # Build match list to return
1090 # Build match list to return
1092 n = len(attr)
1091 n = len(attr)
1093 return ["%s.%s" % (expr, w) for w in words if w[:n] == attr ]
1092 return ["%s.%s" % (expr, w) for w in words if w[:n] == attr]
1094
1095
1093
1096 def _evaluate_expr(self, expr):
1094 def _evaluate_expr(self, expr):
1097 obj = not_found
1095 obj = not_found
@@ -1103,13 +1101,13 b' class Completer(Configurable):'
1103 EvaluationContext(
1101 EvaluationContext(
1104 globals_=self.global_namespace,
1102 globals_=self.global_namespace,
1105 locals_=self.namespace,
1103 locals_=self.namespace,
1106 evaluation=self.evaluation
1104 evaluation=self.evaluation,
1107 )
1105 ),
1108 )
1106 )
1109 done = True
1107 done = True
1110 except Exception as e:
1108 except Exception as e:
1111 if self.debug:
1109 if self.debug:
1112 print('Evaluation exception', e)
1110 print("Evaluation exception", e)
1113 # trim the expression to remove any invalid prefix
1111 # trim the expression to remove any invalid prefix
1114 # e.g. user starts `(d[`, so we get `expr = '(d'`,
1112 # e.g. user starts `(d[`, so we get `expr = '(d'`,
1115 # where parenthesis is not closed.
1113 # where parenthesis is not closed.
@@ -1135,6 +1133,7 b' class DictKeyState(enum.Flag):'
1135 - given `d3 = {('a', 'b'): 1}`: `d3['<tab>` will yield `{'a': IN_TUPLE}` as `'a'` can be added.
1133 - given `d3 = {('a', 'b'): 1}`: `d3['<tab>` will yield `{'a': IN_TUPLE}` as `'a'` can be added.
1136 - given `d4 = {'a': 1, ('a', 'b'): 2}`: `d4['<tab>` will yield `{'a': END_OF_ITEM & END_OF_TUPLE}`
1134 - given `d4 = {'a': 1, ('a', 'b'): 2}`: `d4['<tab>` will yield `{'a': END_OF_ITEM & END_OF_TUPLE}`
1137 """
1135 """
1136
1138 BASELINE = 0
1137 BASELINE = 0
1139 END_OF_ITEM = enum.auto()
1138 END_OF_ITEM = enum.auto()
1140 END_OF_TUPLE = enum.auto()
1139 END_OF_TUPLE = enum.auto()
@@ -1179,9 +1178,9 b' def _match_number_in_dict_key_prefix(prefix: str) -> Union[str, None]:'
1179 # we did not match a number
1178 # we did not match a number
1180 return None
1179 return None
1181 if token.type == tokenize.OP:
1180 if token.type == tokenize.OP:
1182 if token.string == ',':
1181 if token.string == ",":
1183 break
1182 break
1184 if token.string in {'+', '-'}:
1183 if token.string in {"+", "-"}:
1185 number = token.string + number
1184 number = token.string + number
1186 else:
1185 else:
1187 return None
1186 return None
@@ -1189,9 +1188,9 b' def _match_number_in_dict_key_prefix(prefix: str) -> Union[str, None]:'
1189
1188
1190
1189
1191 _INT_FORMATS = {
1190 _INT_FORMATS = {
1192 '0b': bin,
1191 "0b": bin,
1193 '0o': oct,
1192 "0o": oct,
1194 '0x': hex,
1193 "0x": hex,
1195 }
1194 }
1196
1195
1197
1196
@@ -1199,7 +1198,7 b' def match_dict_keys('
1199 keys: List[Union[str, bytes, Tuple[Union[str, bytes], ...]]],
1198 keys: List[Union[str, bytes, Tuple[Union[str, bytes], ...]]],
1200 prefix: str,
1199 prefix: str,
1201 delims: str,
1200 delims: str,
1202 extra_prefix: Optional[Tuple[Union[str, bytes], ...]] = None
1201 extra_prefix: Optional[Tuple[Union[str, bytes], ...]] = None,
1203 ) -> Tuple[str, int, Dict[str, DictKeyState]]:
1202 ) -> Tuple[str, int, Dict[str, DictKeyState]]:
1204 """Used by dict_key_matches, matching the prefix to a list of keys
1203 """Used by dict_key_matches, matching the prefix to a list of keys
1205
1204
@@ -1225,11 +1224,13 b' def match_dict_keys('
1225 """
1224 """
1226 prefix_tuple = extra_prefix if extra_prefix else ()
1225 prefix_tuple = extra_prefix if extra_prefix else ()
1227
1226
1228 prefix_tuple_size = sum([
1227 prefix_tuple_size = sum(
1229 # for pandas, do not count slices as taking space
1228 [
1230 not isinstance(k, slice)
1229 # for pandas, do not count slices as taking space
1231 for k in prefix_tuple
1230 not isinstance(k, slice)
1232 ])
1231 for k in prefix_tuple
1232 ]
1233 )
1233 text_serializable_types = (str, bytes, int, float, slice)
1234 text_serializable_types = (str, bytes, int, float, slice)
1234
1235
1235 def filter_prefix_tuple(key):
1236 def filter_prefix_tuple(key):
@@ -1247,7 +1248,9 b' def match_dict_keys('
1247 # All checks passed!
1248 # All checks passed!
1248 return True
1249 return True
1249
1250
1250 filtered_key_is_final: Dict[Union[str, bytes, int, float], DictKeyState] = defaultdict(lambda: DictKeyState.BASELINE)
1251 filtered_key_is_final: Dict[
1252 Union[str, bytes, int, float], DictKeyState
1253 ] = defaultdict(lambda: DictKeyState.BASELINE)
1251
1254
1252 for k in keys:
1255 for k in keys:
1253 # If at least one of the matches is not final, mark as undetermined.
1256 # If at least one of the matches is not final, mark as undetermined.
@@ -1259,8 +1262,8 b' def match_dict_keys('
1259 key_fragment = k[prefix_tuple_size]
1262 key_fragment = k[prefix_tuple_size]
1260 filtered_key_is_final[key_fragment] |= (
1263 filtered_key_is_final[key_fragment] |= (
1261 DictKeyState.END_OF_TUPLE
1264 DictKeyState.END_OF_TUPLE
1262 if len(k) == prefix_tuple_size + 1 else
1265 if len(k) == prefix_tuple_size + 1
1263 DictKeyState.IN_TUPLE
1266 else DictKeyState.IN_TUPLE
1264 )
1267 )
1265 elif prefix_tuple_size > 0:
1268 elif prefix_tuple_size > 0:
1266 # we are completing a tuple but this key is not a tuple,
1269 # we are completing a tuple but this key is not a tuple,
@@ -1273,9 +1276,9 b' def match_dict_keys('
1273 filtered_keys = filtered_key_is_final.keys()
1276 filtered_keys = filtered_key_is_final.keys()
1274
1277
1275 if not prefix:
1278 if not prefix:
1276 return '', 0, {repr(k): v for k, v in filtered_key_is_final.items()}
1279 return "", 0, {repr(k): v for k, v in filtered_key_is_final.items()}
1277
1280
1278 quote_match = re.search('(?:"|\')', prefix)
1281 quote_match = re.search("(?:\"|')", prefix)
1279 is_user_prefix_numeric = False
1282 is_user_prefix_numeric = False
1280
1283
1281 if quote_match:
1284 if quote_match:
@@ -1284,7 +1287,7 b' def match_dict_keys('
1284 try:
1287 try:
1285 prefix_str = literal_eval(valid_prefix)
1288 prefix_str = literal_eval(valid_prefix)
1286 except Exception:
1289 except Exception:
1287 return '', 0, {}
1290 return "", 0, {}
1288 else:
1291 else:
1289 # If it does not look like a string, let's assume
1292 # If it does not look like a string, let's assume
1290 # we are dealing with a number or variable.
1293 # we are dealing with a number or variable.
@@ -1294,11 +1297,11 b' def match_dict_keys('
1294 if number_match is None:
1297 if number_match is None:
1295 # The alternative would be to assume that user forgort the quote
1298 # The alternative would be to assume that user forgort the quote
1296 # and if the substring matches, suggest adding it at the start.
1299 # and if the substring matches, suggest adding it at the start.
1297 return '', 0, {}
1300 return "", 0, {}
1298
1301
1299 prefix_str = number_match
1302 prefix_str = number_match
1300 is_user_prefix_numeric = True
1303 is_user_prefix_numeric = True
1301 quote = ''
1304 quote = ""
1302
1305
1303 pattern = '[^' + ''.join('\\' + c for c in delims) + ']*$'
1306 pattern = '[^' + ''.join('\\' + c for c in delims) + ']*$'
1304 token_match = re.search(pattern, prefix, re.UNICODE)
1307 token_match = re.search(pattern, prefix, re.UNICODE)
@@ -1333,7 +1336,7 b' def match_dict_keys('
1333 continue
1336 continue
1334
1337
1335 # reformat remainder of key to begin with prefix
1338 # reformat remainder of key to begin with prefix
1336 rem = str_key[len(prefix_str):]
1339 rem = str_key[len(prefix_str) :]
1337 # force repr wrapped in '
1340 # force repr wrapped in '
1338 rem_repr = repr(rem + '"') if isinstance(rem, str) else repr(rem + b'"')
1341 rem_repr = repr(rem + '"') if isinstance(rem, str) else repr(rem + b'"')
1339 rem_repr = rem_repr[1 + rem_repr.index("'"):-2]
1342 rem_repr = rem_repr[1 + rem_repr.index("'"):-2]
@@ -1344,7 +1347,7 b' def match_dict_keys('
1344 rem_repr = rem_repr.replace('"', '\\"')
1347 rem_repr = rem_repr.replace('"', '\\"')
1345
1348
1346 # then reinsert prefix from start of token
1349 # then reinsert prefix from start of token
1347 match = '%s%s' % (token_prefix, rem_repr)
1350 match = "%s%s" % (token_prefix, rem_repr)
1348
1351
1349 matched[match] = filtered_key_is_final[key]
1352 matched[match] = filtered_key_is_final[key]
1350 return quote, token_start, matched
1353 return quote, token_start, matched
@@ -1572,7 +1575,8 b' def _make_signature(completion)-> str:'
1572 _CompleteResult = Dict[str, MatcherResult]
1575 _CompleteResult = Dict[str, MatcherResult]
1573
1576
1574
1577
1575 DICT_MATCHER_REGEX = re.compile(r"""(?x)
1578 DICT_MATCHER_REGEX = re.compile(
1579 r"""(?x)
1576 ( # match dict-referring - or any get item object - expression
1580 ( # match dict-referring - or any get item object - expression
1577 .+
1581 .+
1578 )
1582 )
@@ -1616,7 +1620,9 b' DICT_MATCHER_REGEX = re.compile(r"""(?x)'
1616 )
1620 )
1617 )?
1621 )?
1618 $
1622 $
1619 """)
1623 """
1624 )
1625
1620
1626
1621 def _convert_matcher_v1_result_to_v2(
1627 def _convert_matcher_v1_result_to_v2(
1622 matches: Sequence[str],
1628 matches: Sequence[str],
@@ -1640,12 +1646,12 b' class IPCompleter(Completer):'
1640 @observe('greedy')
1646 @observe('greedy')
1641 def _greedy_changed(self, change):
1647 def _greedy_changed(self, change):
1642 """update the splitter and readline delims when greedy is changed"""
1648 """update the splitter and readline delims when greedy is changed"""
1643 if change['new']:
1649 if change["new"]:
1644 self.evaluation = 'unsafe'
1650 self.evaluation = "unsafe"
1645 self.auto_close_dict_keys = True
1651 self.auto_close_dict_keys = True
1646 self.splitter.delims = GREEDY_DELIMS
1652 self.splitter.delims = GREEDY_DELIMS
1647 else:
1653 else:
1648 self.evaluation = 'limitted'
1654 self.evaluation = "limitted"
1649 self.auto_close_dict_keys = False
1655 self.auto_close_dict_keys = False
1650 self.splitter.delims = DELIMS
1656 self.splitter.delims = DELIMS
1651
1657
@@ -2375,13 +2381,12 b' class IPCompleter(Completer):'
2375 return method()
2381 return method()
2376
2382
2377 # Special case some common in-memory dict-like types
2383 # Special case some common in-memory dict-like types
2378 if (isinstance(obj, dict) or
2384 if isinstance(obj, dict) or _safe_isinstance(obj, "pandas", "DataFrame"):
2379 _safe_isinstance(obj, 'pandas', 'DataFrame')):
2380 try:
2385 try:
2381 return list(obj.keys())
2386 return list(obj.keys())
2382 except Exception:
2387 except Exception:
2383 return []
2388 return []
2384 elif _safe_isinstance(obj, 'pandas', 'core', 'indexing', '_LocIndexer'):
2389 elif _safe_isinstance(obj, "pandas", "core", "indexing", "_LocIndexer"):
2385 try:
2390 try:
2386 return list(obj.obj.keys())
2391 return list(obj.obj.keys())
2387 except Exception:
2392 except Exception:
@@ -2408,7 +2413,7 b' class IPCompleter(Completer):'
2408
2413
2409 # Short-circuit on closed dictionary (regular expression would
2414 # Short-circuit on closed dictionary (regular expression would
2410 # not match anyway, but would take quite a while).
2415 # not match anyway, but would take quite a while).
2411 if self.text_until_cursor.strip().endswith(']'):
2416 if self.text_until_cursor.strip().endswith("]"):
2412 return []
2417 return []
2413
2418
2414 match = DICT_MATCHER_REGEX.search(self.text_until_cursor)
2419 match = DICT_MATCHER_REGEX.search(self.text_until_cursor)
@@ -2433,15 +2438,12 b' class IPCompleter(Completer):'
2433 globals_=self.global_namespace,
2438 globals_=self.global_namespace,
2434 locals_=self.namespace,
2439 locals_=self.namespace,
2435 evaluation=self.evaluation,
2440 evaluation=self.evaluation,
2436 in_subscript=True
2441 in_subscript=True,
2437 )
2442 ),
2438 )
2443 )
2439
2444
2440 closing_quote, token_offset, matches = match_dict_keys(
2445 closing_quote, token_offset, matches = match_dict_keys(
2441 keys,
2446 keys, key_prefix, self.splitter.delims, extra_prefix=tuple_prefix
2442 key_prefix,
2443 self.splitter.delims,
2444 extra_prefix=tuple_prefix
2445 )
2447 )
2446 if not matches:
2448 if not matches:
2447 return []
2449 return []
@@ -2469,11 +2471,11 b' class IPCompleter(Completer):'
2469 can_close_quote = False
2471 can_close_quote = False
2470 can_close_bracket = False
2472 can_close_bracket = False
2471
2473
2472 continuation = self.line_buffer[len(self.text_until_cursor):].strip()
2474 continuation = self.line_buffer[len(self.text_until_cursor) :].strip()
2473
2475
2474 if continuation.startswith(closing_quote):
2476 if continuation.startswith(closing_quote):
2475 # do not close if already closed, e.g. `d['a<tab>'`
2477 # do not close if already closed, e.g. `d['a<tab>'`
2476 continuation = continuation[len(closing_quote):]
2478 continuation = continuation[len(closing_quote) :]
2477 else:
2479 else:
2478 can_close_quote = True
2480 can_close_quote = True
2479
2481
@@ -2483,8 +2485,14 b' class IPCompleter(Completer):'
2483 # handling it is out of scope, so let's avoid appending suffixes.
2485 # handling it is out of scope, so let's avoid appending suffixes.
2484 has_known_tuple_handling = isinstance(obj, dict)
2486 has_known_tuple_handling = isinstance(obj, dict)
2485
2487
2486 can_close_bracket = not continuation.startswith(']') and self.auto_close_dict_keys
2488 can_close_bracket = (
2487 can_close_tuple_item = not continuation.startswith(',') and has_known_tuple_handling and self.auto_close_dict_keys
2489 not continuation.startswith("]") and self.auto_close_dict_keys
2490 )
2491 can_close_tuple_item = (
2492 not continuation.startswith(",")
2493 and has_known_tuple_handling
2494 and self.auto_close_dict_keys
2495 )
2488 can_close_quote = can_close_quote and self.auto_close_dict_keys
2496 can_close_quote = can_close_quote and self.auto_close_dict_keys
2489
2497
2490 # fast path if closing qoute should be appended but not suffix is allowed
2498 # fast path if closing qoute should be appended but not suffix is allowed
@@ -2507,9 +2515,9 b' class IPCompleter(Completer):'
2507 pass
2515 pass
2508
2516
2509 if state_flag in end_of_tuple_or_item and can_close_bracket:
2517 if state_flag in end_of_tuple_or_item and can_close_bracket:
2510 result += ']'
2518 result += "]"
2511 if state_flag == DictKeyState.IN_TUPLE and can_close_tuple_item:
2519 if state_flag == DictKeyState.IN_TUPLE and can_close_tuple_item:
2512 result += ', '
2520 result += ", "
2513 results.append(result)
2521 results.append(result)
2514 return results
2522 return results
2515
2523
@@ -16,20 +16,24 b' else:'
16
16
17
17
18 class HasGetItem(Protocol):
18 class HasGetItem(Protocol):
19 def __getitem__(self, key) -> None: ...
19 def __getitem__(self, key) -> None:
20 ...
20
21
21
22
22 class InstancesHaveGetItem(Protocol):
23 class InstancesHaveGetItem(Protocol):
23 def __call__(self) -> HasGetItem: ...
24 def __call__(self) -> HasGetItem:
25 ...
24
26
25
27
26 class HasGetAttr(Protocol):
28 class HasGetAttr(Protocol):
27 def __getattr__(self, key) -> None: ...
29 def __getattr__(self, key) -> None:
30 ...
28
31
29
32
30 class DoesNotHaveGetAttr(Protocol):
33 class DoesNotHaveGetAttr(Protocol):
31 pass
34 pass
32
35
36
33 # By default `__getattr__` is not explicitly implemented on most objects
37 # By default `__getattr__` is not explicitly implemented on most objects
34 MayHaveGetattr = Union[HasGetAttr, DoesNotHaveGetAttr]
38 MayHaveGetattr = Union[HasGetAttr, DoesNotHaveGetAttr]
35
39
@@ -38,22 +42,16 b' def unbind_method(func: Callable) -> Union[Callable, None]:'
38 """Get unbound method for given bound method.
42 """Get unbound method for given bound method.
39
43
40 Returns None if cannot get unbound method."""
44 Returns None if cannot get unbound method."""
41 owner = getattr(func, '__self__', None)
45 owner = getattr(func, "__self__", None)
42 owner_class = type(owner)
46 owner_class = type(owner)
43 name = getattr(func, '__name__', None)
47 name = getattr(func, "__name__", None)
44 instance_dict_overrides = getattr(owner, '__dict__', None)
48 instance_dict_overrides = getattr(owner, "__dict__", None)
45 if (
49 if (
46 owner is not None
50 owner is not None
47 and
51 and name
48 name
52 and (
49 and
50 (
51 not instance_dict_overrides
53 not instance_dict_overrides
52 or
54 or (instance_dict_overrides and name not in instance_dict_overrides)
53 (
54 instance_dict_overrides
55 and name not in instance_dict_overrides
56 )
57 )
55 )
58 ):
56 ):
59 return getattr(owner_class, name)
57 return getattr(owner_class, name)
@@ -86,7 +84,13 b' class EvaluationPolicy:'
86 if owner_method and owner_method in self.allowed_calls:
84 if owner_method and owner_method in self.allowed_calls:
87 return True
85 return True
88
86
89 def has_original_dunder_external(value, module_name, access_path, method_name,):
87
88 def has_original_dunder_external(
89 value,
90 module_name,
91 access_path,
92 method_name,
93 ):
90 try:
94 try:
91 if module_name not in sys.modules:
95 if module_name not in sys.modules:
92 return False
96 return False
@@ -106,11 +110,7 b' def has_original_dunder_external(value, module_name, access_path, method_name,):'
106
110
107
111
108 def has_original_dunder(
112 def has_original_dunder(
109 value,
113 value, allowed_types, allowed_methods, allowed_external, method_name
110 allowed_types,
111 allowed_methods,
112 allowed_external,
113 method_name
114 ):
114 ):
115 # note: Python ignores `__getattr__`/`__getitem__` on instances,
115 # note: Python ignores `__getattr__`/`__getitem__` on instances,
116 # we only need to check at class level
116 # we only need to check at class level
@@ -148,14 +148,14 b' class SelectivePolicy(EvaluationPolicy):'
148 allowed_types=self.allowed_getattr,
148 allowed_types=self.allowed_getattr,
149 allowed_methods=self._getattribute_methods,
149 allowed_methods=self._getattribute_methods,
150 allowed_external=self.allowed_getattr_external,
150 allowed_external=self.allowed_getattr_external,
151 method_name='__getattribute__'
151 method_name="__getattribute__",
152 )
152 )
153 has_original_attr = has_original_dunder(
153 has_original_attr = has_original_dunder(
154 value,
154 value,
155 allowed_types=self.allowed_getattr,
155 allowed_types=self.allowed_getattr,
156 allowed_methods=self._getattr_methods,
156 allowed_methods=self._getattr_methods,
157 allowed_external=self.allowed_getattr_external,
157 allowed_external=self.allowed_getattr_external,
158 method_name='__getattr__'
158 method_name="__getattr__",
159 )
159 )
160 # Many objects do not have `__getattr__`, this is fine
160 # Many objects do not have `__getattr__`, this is fine
161 if has_original_attr is None and has_original_attribute:
161 if has_original_attr is None and has_original_attribute:
@@ -168,7 +168,6 b' class SelectivePolicy(EvaluationPolicy):'
168 if self.can_get_attr(value, attr):
168 if self.can_get_attr(value, attr):
169 return getattr(value, attr)
169 return getattr(value, attr)
170
170
171
172 def can_get_item(self, value, item):
171 def can_get_item(self, value, item):
173 """Allow accessing `__getiitem__` of allow-listed instances unless it was not modified."""
172 """Allow accessing `__getiitem__` of allow-listed instances unless it was not modified."""
174 return has_original_dunder(
173 return has_original_dunder(
@@ -176,29 +175,20 b' class SelectivePolicy(EvaluationPolicy):'
176 allowed_types=self.allowed_getitem,
175 allowed_types=self.allowed_getitem,
177 allowed_methods=self._getitem_methods,
176 allowed_methods=self._getitem_methods,
178 allowed_external=self.allowed_getitem_external,
177 allowed_external=self.allowed_getitem_external,
179 method_name='__getitem__'
178 method_name="__getitem__",
180 )
179 )
181
180
182 @cached_property
181 @cached_property
183 def _getitem_methods(self) -> Set[Callable]:
182 def _getitem_methods(self) -> Set[Callable]:
184 return self._safe_get_methods(
183 return self._safe_get_methods(self.allowed_getitem, "__getitem__")
185 self.allowed_getitem,
186 '__getitem__'
187 )
188
184
189 @cached_property
185 @cached_property
190 def _getattr_methods(self) -> Set[Callable]:
186 def _getattr_methods(self) -> Set[Callable]:
191 return self._safe_get_methods(
187 return self._safe_get_methods(self.allowed_getattr, "__getattr__")
192 self.allowed_getattr,
193 '__getattr__'
194 )
195
188
196 @cached_property
189 @cached_property
197 def _getattribute_methods(self) -> Set[Callable]:
190 def _getattribute_methods(self) -> Set[Callable]:
198 return self._safe_get_methods(
191 return self._safe_get_methods(self.allowed_getattr, "__getattribute__")
199 self.allowed_getattr,
200 '__getattribute__'
201 )
202
192
203 def _safe_get_methods(self, classes, name) -> Set[Callable]:
193 def _safe_get_methods(self, classes, name) -> Set[Callable]:
204 return {
194 return {
@@ -216,7 +206,9 b' class DummyNamedTuple(NamedTuple):'
216 class EvaluationContext(NamedTuple):
206 class EvaluationContext(NamedTuple):
217 locals_: dict
207 locals_: dict
218 globals_: dict
208 globals_: dict
219 evaluation: Literal['forbidden', 'minimal', 'limitted', 'unsafe', 'dangerous'] = 'forbidden'
209 evaluation: Literal[
210 "forbidden", "minimal", "limitted", "unsafe", "dangerous"
211 ] = "forbidden"
220 in_subscript: bool = False
212 in_subscript: bool = False
221
213
222
214
@@ -224,21 +216,20 b' class IdentitySubscript:'
224 def __getitem__(self, key):
216 def __getitem__(self, key):
225 return key
217 return key
226
218
219
227 IDENTITY_SUBSCRIPT = IdentitySubscript()
220 IDENTITY_SUBSCRIPT = IdentitySubscript()
228 SUBSCRIPT_MARKER = '__SUBSCRIPT_SENTINEL__'
221 SUBSCRIPT_MARKER = "__SUBSCRIPT_SENTINEL__"
222
229
223
230 class GuardRejection(ValueError):
224 class GuardRejection(ValueError):
231 pass
225 pass
232
226
233
227
234 def guarded_eval(
228 def guarded_eval(code: str, context: EvaluationContext):
235 code: str,
236 context: EvaluationContext
237 ):
238 locals_ = context.locals_
229 locals_ = context.locals_
239
230
240 if context.evaluation == 'forbidden':
231 if context.evaluation == "forbidden":
241 raise GuardRejection('Forbidden mode')
232 raise GuardRejection("Forbidden mode")
242
233
243 # note: not using `ast.literal_eval` as it does not implement
234 # note: not using `ast.literal_eval` as it does not implement
244 # getitem at all, for example it fails on simple `[0][1]`
235 # getitem at all, for example it fails on simple `[0][1]`
@@ -252,19 +243,17 b' def guarded_eval('
252 return tuple()
243 return tuple()
253 locals_ = locals_.copy()
244 locals_ = locals_.copy()
254 locals_[SUBSCRIPT_MARKER] = IDENTITY_SUBSCRIPT
245 locals_[SUBSCRIPT_MARKER] = IDENTITY_SUBSCRIPT
255 code = SUBSCRIPT_MARKER + '[' + code + ']'
246 code = SUBSCRIPT_MARKER + "[" + code + "]"
256 context = EvaluationContext(**{
247 context = EvaluationContext(**{**context._asdict(), **{"locals_": locals_}})
257 **context._asdict(),
258 **{'locals_': locals_}
259 })
260
248
261 if context.evaluation == 'dangerous':
249 if context.evaluation == "dangerous":
262 return eval(code, context.globals_, context.locals_)
250 return eval(code, context.globals_, context.locals_)
263
251
264 expression = ast.parse(code, mode='eval')
252 expression = ast.parse(code, mode="eval")
265
253
266 return eval_node(expression, context)
254 return eval_node(expression, context)
267
255
256
268 def eval_node(node: Union[ast.AST, None], context: EvaluationContext):
257 def eval_node(node: Union[ast.AST, None], context: EvaluationContext):
269 """
258 """
270 Evaluate AST node in provided context.
259 Evaluate AST node in provided context.
@@ -314,7 +303,7 b' def eval_node(node: Union[ast.AST, None], context: EvaluationContext):'
314 if isinstance(node.op, ast.Mod):
303 if isinstance(node.op, ast.Mod):
315 return left % right
304 return left % right
316 if isinstance(node.op, ast.Pow):
305 if isinstance(node.op, ast.Pow):
317 return left ** right
306 return left**right
318 if isinstance(node.op, ast.LShift):
307 if isinstance(node.op, ast.LShift):
319 return left << right
308 return left << right
320 if isinstance(node.op, ast.RShift):
309 if isinstance(node.op, ast.RShift):
@@ -332,36 +321,26 b' def eval_node(node: Union[ast.AST, None], context: EvaluationContext):'
332 if isinstance(node, ast.Index):
321 if isinstance(node, ast.Index):
333 return eval_node(node.value, context)
322 return eval_node(node.value, context)
334 if isinstance(node, ast.Tuple):
323 if isinstance(node, ast.Tuple):
335 return tuple(
324 return tuple(eval_node(e, context) for e in node.elts)
336 eval_node(e, context)
337 for e in node.elts
338 )
339 if isinstance(node, ast.List):
325 if isinstance(node, ast.List):
340 return [
326 return [eval_node(e, context) for e in node.elts]
341 eval_node(e, context)
342 for e in node.elts
343 ]
344 if isinstance(node, ast.Set):
327 if isinstance(node, ast.Set):
345 return {
328 return {eval_node(e, context) for e in node.elts}
346 eval_node(e, context)
347 for e in node.elts
348 }
349 if isinstance(node, ast.Dict):
329 if isinstance(node, ast.Dict):
350 return dict(zip(
330 return dict(
351 [eval_node(k, context) for k in node.keys],
331 zip(
352 [eval_node(v, context) for v in node.values]
332 [eval_node(k, context) for k in node.keys],
353 ))
333 [eval_node(v, context) for v in node.values],
334 )
335 )
354 if isinstance(node, ast.Slice):
336 if isinstance(node, ast.Slice):
355 return slice(
337 return slice(
356 eval_node(node.lower, context),
338 eval_node(node.lower, context),
357 eval_node(node.upper, context),
339 eval_node(node.upper, context),
358 eval_node(node.step, context)
340 eval_node(node.step, context),
359 )
341 )
360 if isinstance(node, ast.ExtSlice):
342 if isinstance(node, ast.ExtSlice):
361 return tuple([
343 return tuple([eval_node(dim, context) for dim in node.dims])
362 eval_node(dim, context)
363 for dim in node.dims
364 ])
365 if isinstance(node, ast.UnaryOp):
344 if isinstance(node, ast.UnaryOp):
366 # TODO: add guards
345 # TODO: add guards
367 value = eval_node(node.operand, context)
346 value = eval_node(node.operand, context)
@@ -373,16 +352,16 b' def eval_node(node: Union[ast.AST, None], context: EvaluationContext):'
373 return ~value
352 return ~value
374 if isinstance(node.op, ast.Not):
353 if isinstance(node.op, ast.Not):
375 return not value
354 return not value
376 raise ValueError('Unhandled unary operation:', node.op)
355 raise ValueError("Unhandled unary operation:", node.op)
377 if isinstance(node, ast.Subscript):
356 if isinstance(node, ast.Subscript):
378 value = eval_node(node.value, context)
357 value = eval_node(node.value, context)
379 slice_ = eval_node(node.slice, context)
358 slice_ = eval_node(node.slice, context)
380 if policy.can_get_item(value, slice_):
359 if policy.can_get_item(value, slice_):
381 return value[slice_]
360 return value[slice_]
382 raise GuardRejection(
361 raise GuardRejection(
383 'Subscript access (`__getitem__`) for',
362 "Subscript access (`__getitem__`) for",
384 type(value), # not joined to avoid calling `repr`
363 type(value), # not joined to avoid calling `repr`
385 f' not allowed in {context.evaluation} mode'
364 f" not allowed in {context.evaluation} mode",
386 )
365 )
387 if isinstance(node, ast.Name):
366 if isinstance(node, ast.Name):
388 if policy.allow_locals_access and node.id in context.locals_:
367 if policy.allow_locals_access and node.id in context.locals_:
@@ -393,49 +372,46 b' def eval_node(node: Union[ast.AST, None], context: EvaluationContext):'
393 return __builtins__[node.id]
372 return __builtins__[node.id]
394 if not policy.allow_globals_access and not policy.allow_locals_access:
373 if not policy.allow_globals_access and not policy.allow_locals_access:
395 raise GuardRejection(
374 raise GuardRejection(
396 f'Namespace access not allowed in {context.evaluation} mode'
375 f"Namespace access not allowed in {context.evaluation} mode"
397 )
376 )
398 else:
377 else:
399 raise NameError(f'{node.id} not found in locals nor globals')
378 raise NameError(f"{node.id} not found in locals nor globals")
400 if isinstance(node, ast.Attribute):
379 if isinstance(node, ast.Attribute):
401 value = eval_node(node.value, context)
380 value = eval_node(node.value, context)
402 if policy.can_get_attr(value, node.attr):
381 if policy.can_get_attr(value, node.attr):
403 return getattr(value, node.attr)
382 return getattr(value, node.attr)
404 raise GuardRejection(
383 raise GuardRejection(
405 'Attribute access (`__getattr__`) for',
384 "Attribute access (`__getattr__`) for",
406 type(value), # not joined to avoid calling `repr`
385 type(value), # not joined to avoid calling `repr`
407 f'not allowed in {context.evaluation} mode'
386 f"not allowed in {context.evaluation} mode",
408 )
387 )
409 if isinstance(node, ast.IfExp):
388 if isinstance(node, ast.IfExp):
410 test = eval_node(node.test, context)
389 test = eval_node(node.test, context)
411 if test:
390 if test:
412 return eval_node(node.body, context)
391 return eval_node(node.body, context)
413 else:
392 else:
414 return eval_node(node.orelse, context)
393 return eval_node(node.orelse, context)
415 if isinstance(node, ast.Call):
394 if isinstance(node, ast.Call):
416 func = eval_node(node.func, context)
395 func = eval_node(node.func, context)
417 print(node.keywords)
396 print(node.keywords)
418 if policy.can_call(func) and not node.keywords:
397 if policy.can_call(func) and not node.keywords:
419 args = [
398 args = [eval_node(arg, context) for arg in node.args]
420 eval_node(arg, context)
421 for arg in node.args
422 ]
423 return func(*args)
399 return func(*args)
424 raise GuardRejection(
400 raise GuardRejection(
425 'Call for',
401 "Call for",
426 func, # not joined to avoid calling `repr`
402 func, # not joined to avoid calling `repr`
427 f'not allowed in {context.evaluation} mode'
403 f"not allowed in {context.evaluation} mode",
428 )
404 )
429 raise ValueError('Unhandled node', node)
405 raise ValueError("Unhandled node", node)
430
406
431
407
432 SUPPORTED_EXTERNAL_GETITEM = {
408 SUPPORTED_EXTERNAL_GETITEM = {
433 ('pandas', 'core', 'indexing', '_iLocIndexer'),
409 ("pandas", "core", "indexing", "_iLocIndexer"),
434 ('pandas', 'core', 'indexing', '_LocIndexer'),
410 ("pandas", "core", "indexing", "_LocIndexer"),
435 ('pandas', 'DataFrame'),
411 ("pandas", "DataFrame"),
436 ('pandas', 'Series'),
412 ("pandas", "Series"),
437 ('numpy', 'ndarray'),
413 ("numpy", "ndarray"),
438 ('numpy', 'void')
414 ("numpy", "void"),
439 }
415 }
440
416
441 BUILTIN_GETITEM = {
417 BUILTIN_GETITEM = {
@@ -452,20 +428,17 b' BUILTIN_GETITEM = {'
452 collections.UserList,
428 collections.UserList,
453 collections.UserString,
429 collections.UserString,
454 DummyNamedTuple,
430 DummyNamedTuple,
455 IdentitySubscript
431 IdentitySubscript,
456 }
432 }
457
433
458
434
459 def _list_methods(cls, source=None):
435 def _list_methods(cls, source=None):
460 """For use on immutable objects or with methods returning a copy"""
436 """For use on immutable objects or with methods returning a copy"""
461 return [
437 return [getattr(cls, k) for k in (source if source else dir(cls))]
462 getattr(cls, k)
463 for k in (source if source else dir(cls))
464 ]
465
438
466
439
467 dict_non_mutating_methods = ('copy', 'keys', 'values', 'items')
440 dict_non_mutating_methods = ("copy", "keys", "values", "items")
468 list_non_mutating_methods = ('copy', 'index', 'count')
441 list_non_mutating_methods = ("copy", "index", "count")
469 set_non_mutating_methods = set(dir(set)) & set(dir(frozenset))
442 set_non_mutating_methods = set(dir(set)) & set(dir(frozenset))
470
443
471
444
@@ -504,20 +477,20 b' ALLOWED_CALLS = {'
504 collections.Counter,
477 collections.Counter,
505 *_list_methods(collections.Counter, dict_non_mutating_methods),
478 *_list_methods(collections.Counter, dict_non_mutating_methods),
506 collections.Counter.elements,
479 collections.Counter.elements,
507 collections.Counter.most_common
480 collections.Counter.most_common,
508 }
481 }
509
482
510 EVALUATION_POLICIES = {
483 EVALUATION_POLICIES = {
511 'minimal': EvaluationPolicy(
484 "minimal": EvaluationPolicy(
512 allow_builtins_access=True,
485 allow_builtins_access=True,
513 allow_locals_access=False,
486 allow_locals_access=False,
514 allow_globals_access=False,
487 allow_globals_access=False,
515 allow_item_access=False,
488 allow_item_access=False,
516 allow_attr_access=False,
489 allow_attr_access=False,
517 allowed_calls=set(),
490 allowed_calls=set(),
518 allow_any_calls=False
491 allow_any_calls=False,
519 ),
492 ),
520 'limitted': SelectivePolicy(
493 "limitted": SelectivePolicy(
521 # TODO:
494 # TODO:
522 # - should reject binary and unary operations if custom methods would be dispatched
495 # - should reject binary and unary operations if custom methods would be dispatched
523 allowed_getitem=BUILTIN_GETITEM,
496 allowed_getitem=BUILTIN_GETITEM,
@@ -529,24 +502,24 b' EVALUATION_POLICIES = {'
529 object,
502 object,
530 type, # `type` handles a lot of generic cases, e.g. numbers as in `int.real`.
503 type, # `type` handles a lot of generic cases, e.g. numbers as in `int.real`.
531 dict_keys,
504 dict_keys,
532 method_descriptor
505 method_descriptor,
533 },
506 },
534 allowed_getattr_external={
507 allowed_getattr_external={
535 # pandas Series/Frame implements custom `__getattr__`
508 # pandas Series/Frame implements custom `__getattr__`
536 ('pandas', 'DataFrame'),
509 ("pandas", "DataFrame"),
537 ('pandas', 'Series')
510 ("pandas", "Series"),
538 },
511 },
539 allow_builtins_access=True,
512 allow_builtins_access=True,
540 allow_locals_access=True,
513 allow_locals_access=True,
541 allow_globals_access=True,
514 allow_globals_access=True,
542 allowed_calls=ALLOWED_CALLS
515 allowed_calls=ALLOWED_CALLS,
543 ),
516 ),
544 'unsafe': EvaluationPolicy(
517 "unsafe": EvaluationPolicy(
545 allow_builtins_access=True,
518 allow_builtins_access=True,
546 allow_locals_access=True,
519 allow_locals_access=True,
547 allow_globals_access=True,
520 allow_globals_access=True,
548 allow_attr_access=True,
521 allow_attr_access=True,
549 allow_item_access=True,
522 allow_item_access=True,
550 allow_any_calls=True
523 allow_any_calls=True,
551 )
524 ),
552 } No newline at end of file
525 }
@@ -868,21 +868,16 b' class TestCompleter(unittest.TestCase):'
868 assert match(keys, '"f', delims=delims) == ('"', 1, ["foo"])
868 assert match(keys, '"f', delims=delims) == ('"', 1, ["foo"])
869
869
870 # Completion on first item of tuple
870 # Completion on first item of tuple
871 keys = [("foo", 1111), ("foo", 2222), (3333, "bar"), (3333, 'test')]
871 keys = [("foo", 1111), ("foo", 2222), (3333, "bar"), (3333, "test")]
872 assert match(keys, "'f", delims=delims) == ("'", 1, ["foo"])
872 assert match(keys, "'f", delims=delims) == ("'", 1, ["foo"])
873 assert match(keys, "33", delims=delims) == ("", 0, ["3333"])
873 assert match(keys, "33", delims=delims) == ("", 0, ["3333"])
874
874
875 # Completion on numbers
875 # Completion on numbers
876 keys = [
876 keys = [0xDEADBEEF, 1111, 1234, "1999", 0b10101, 22] # 3735928559 # 21
877 0xdeadbeef, # 3735928559
878 1111, 1234, "1999",
879 0b10101, # 21
880 22
881 ]
882 assert match(keys, "0xdead", delims=delims) == ("", 0, ["0xdeadbeef"])
877 assert match(keys, "0xdead", delims=delims) == ("", 0, ["0xdeadbeef"])
883 assert match(keys, "1", delims=delims) == ("", 0, ["1111", "1234"])
878 assert match(keys, "1", delims=delims) == ("", 0, ["1111", "1234"])
884 assert match(keys, "2", delims=delims) == ("", 0, ["21", "22"])
879 assert match(keys, "2", delims=delims) == ("", 0, ["21", "22"])
885 assert match(keys, "0b101", delims=delims) == ("", 0, ['0b10101', '0b10110'])
880 assert match(keys, "0b101", delims=delims) == ("", 0, ["0b10101", "0b10110"])
886
881
887 def test_match_dict_keys_tuple(self):
882 def test_match_dict_keys_tuple(self):
888 """
883 """
@@ -898,30 +893,90 b' class TestCompleter(unittest.TestCase):'
898 return quote, offset, list(matches)
893 return quote, offset, list(matches)
899
894
900 # Completion on first key == "foo"
895 # Completion on first key == "foo"
901 assert match(keys, "'", delims=delims, extra_prefix=("foo",)) == ("'", 1, ["bar", "oof"])
896 assert match(keys, "'", delims=delims, extra_prefix=("foo",)) == (
902 assert match(keys, "\"", delims=delims, extra_prefix=("foo",)) == ("\"", 1, ["bar", "oof"])
897 "'",
903 assert match(keys, "'o", delims=delims, extra_prefix=("foo",)) == ("'", 1, ["oof"])
898 1,
904 assert match(keys, "\"o", delims=delims, extra_prefix=("foo",)) == ("\"", 1, ["oof"])
899 ["bar", "oof"],
905 assert match(keys, "b'", delims=delims, extra_prefix=("foo",)) == ("'", 2, ["bar"])
900 )
906 assert match(keys, "b\"", delims=delims, extra_prefix=("foo",)) == ("\"", 2, ["bar"])
901 assert match(keys, '"', delims=delims, extra_prefix=("foo",)) == (
907 assert match(keys, "b'b", delims=delims, extra_prefix=("foo",)) == ("'", 2, ["bar"])
902 '"',
908 assert match(keys, "b\"b", delims=delims, extra_prefix=("foo",)) == ("\"", 2, ["bar"])
903 1,
904 ["bar", "oof"],
905 )
906 assert match(keys, "'o", delims=delims, extra_prefix=("foo",)) == (
907 "'",
908 1,
909 ["oof"],
910 )
911 assert match(keys, '"o', delims=delims, extra_prefix=("foo",)) == (
912 '"',
913 1,
914 ["oof"],
915 )
916 assert match(keys, "b'", delims=delims, extra_prefix=("foo",)) == (
917 "'",
918 2,
919 ["bar"],
920 )
921 assert match(keys, 'b"', delims=delims, extra_prefix=("foo",)) == (
922 '"',
923 2,
924 ["bar"],
925 )
926 assert match(keys, "b'b", delims=delims, extra_prefix=("foo",)) == (
927 "'",
928 2,
929 ["bar"],
930 )
931 assert match(keys, 'b"b', delims=delims, extra_prefix=("foo",)) == (
932 '"',
933 2,
934 ["bar"],
935 )
909
936
910 # No Completion
937 # No Completion
911 assert match(keys, "'", delims=delims, extra_prefix=("no_foo",)) == ("'", 1, [])
938 assert match(keys, "'", delims=delims, extra_prefix=("no_foo",)) == ("'", 1, [])
912 assert match(keys, "'", delims=delims, extra_prefix=("fo",)) == ("'", 1, [])
939 assert match(keys, "'", delims=delims, extra_prefix=("fo",)) == ("'", 1, [])
913
940
914 keys = [('foo1', 'foo2', 'foo3', 'foo4'), ('foo1', 'foo2', 'bar', 'foo4')]
941 keys = [("foo1", "foo2", "foo3", "foo4"), ("foo1", "foo2", "bar", "foo4")]
915 assert match(keys, "'foo", delims=delims, extra_prefix=('foo1',)) == ("'", 1, ["foo2"])
942 assert match(keys, "'foo", delims=delims, extra_prefix=("foo1",)) == (
916 assert match(keys, "'foo", delims=delims, extra_prefix=('foo1', 'foo2')) == ("'", 1, ["foo3"])
943 "'",
917 assert match(keys, "'foo", delims=delims, extra_prefix=('foo1', 'foo2', 'foo3')) == ("'", 1, ["foo4"])
944 1,
918 assert match(keys, "'foo", delims=delims, extra_prefix=('foo1', 'foo2', 'foo3', 'foo4')) == ("'", 1, [])
945 ["foo2"],
946 )
947 assert match(keys, "'foo", delims=delims, extra_prefix=("foo1", "foo2")) == (
948 "'",
949 1,
950 ["foo3"],
951 )
952 assert match(
953 keys, "'foo", delims=delims, extra_prefix=("foo1", "foo2", "foo3")
954 ) == ("'", 1, ["foo4"])
955 assert match(
956 keys, "'foo", delims=delims, extra_prefix=("foo1", "foo2", "foo3", "foo4")
957 ) == ("'", 1, [])
919
958
920 keys = [("foo", 1111), ("foo", "2222"), (3333, "bar"), (3333, 4444)]
959 keys = [("foo", 1111), ("foo", "2222"), (3333, "bar"), (3333, 4444)]
921 assert match(keys, "'", delims=delims, extra_prefix=("foo",)) == ("'", 1, ["2222"])
960 assert match(keys, "'", delims=delims, extra_prefix=("foo",)) == (
922 assert match(keys, "", delims=delims, extra_prefix=("foo",)) == ("", 0, ["1111", "'2222'"])
961 "'",
923 assert match(keys, "'", delims=delims, extra_prefix=(3333,)) == ("'", 1, ["bar"])
962 1,
924 assert match(keys, "", delims=delims, extra_prefix=(3333,)) == ("", 0, ["'bar'", "4444"])
963 ["2222"],
964 )
965 assert match(keys, "", delims=delims, extra_prefix=("foo",)) == (
966 "",
967 0,
968 ["1111", "'2222'"],
969 )
970 assert match(keys, "'", delims=delims, extra_prefix=(3333,)) == (
971 "'",
972 1,
973 ["bar"],
974 )
975 assert match(keys, "", delims=delims, extra_prefix=(3333,)) == (
976 "",
977 0,
978 ["'bar'", "4444"],
979 )
925 assert match(keys, "'", delims=delims, extra_prefix=("3333",)) == ("'", 1, [])
980 assert match(keys, "'", delims=delims, extra_prefix=("3333",)) == ("'", 1, [])
926 assert match(keys, "33", delims=delims) == ("", 0, ["3333"])
981 assert match(keys, "33", delims=delims) == ("", 0, ["3333"])
927
982
@@ -932,16 +987,16 b' class TestCompleter(unittest.TestCase):'
932
987
933 ip.user_ns["d"] = {
988 ip.user_ns["d"] = {
934 # tuple only
989 # tuple only
935 ('aa', 11): None,
990 ("aa", 11): None,
936 # tuple and non-tuple
991 # tuple and non-tuple
937 ('bb', 22): None,
992 ("bb", 22): None,
938 'bb': None,
993 "bb": None,
939 # non-tuple only
994 # non-tuple only
940 'cc': None,
995 "cc": None,
941 # numeric tuple only
996 # numeric tuple only
942 (77, 'x'): None,
997 (77, "x"): None,
943 # numeric tuple and non-tuple
998 # numeric tuple and non-tuple
944 (88, 'y'): None,
999 (88, "y"): None,
945 88: None,
1000 88: None,
946 # numeric non-tuple only
1001 # numeric non-tuple only
947 99: None,
1002 99: None,
@@ -1133,12 +1188,12 b' class TestCompleter(unittest.TestCase):'
1133 complete = ip.Completer.complete
1188 complete = ip.Completer.complete
1134
1189
1135 ip.user_ns["d"] = {
1190 ip.user_ns["d"] = {
1136 0xdeadbeef: None, # 3735928559
1191 0xDEADBEEF: None, # 3735928559
1137 1111: None,
1192 1111: None,
1138 1234: None,
1193 1234: None,
1139 "1999": None,
1194 "1999": None,
1140 0b10101: None, # 21
1195 0b10101: None, # 21
1141 22: None
1196 22: None,
1142 }
1197 }
1143 _, matches = complete(line_buffer="d[1")
1198 _, matches = complete(line_buffer="d[1")
1144 self.assertIn("1111", matches)
1199 self.assertIn("1111", matches)
@@ -1169,7 +1224,7 b' class TestCompleter(unittest.TestCase):'
1169
1224
1170 ip.user_ns["C"] = C
1225 ip.user_ns["C"] = C
1171 ip.user_ns["get"] = lambda: d
1226 ip.user_ns["get"] = lambda: d
1172 ip.user_ns["nested"] = {'x': d}
1227 ip.user_ns["nested"] = {"x": d}
1173
1228
1174 def assert_no_completion(**kwargs):
1229 def assert_no_completion(**kwargs):
1175 _, matches = complete(**kwargs)
1230 _, matches = complete(**kwargs)
@@ -1198,7 +1253,7 b' class TestCompleter(unittest.TestCase):'
1198 # nested dict completion
1253 # nested dict completion
1199 assert_completion(line_buffer="nested['x'][")
1254 assert_completion(line_buffer="nested['x'][")
1200
1255
1201 with evaluation_level('minimal'):
1256 with evaluation_level("minimal"):
1202 with pytest.raises(AssertionError):
1257 with pytest.raises(AssertionError):
1203 assert_completion(line_buffer="nested['x'][")
1258 assert_completion(line_buffer="nested['x'][")
1204
1259
@@ -1289,6 +1344,7 b' class TestCompleter(unittest.TestCase):'
1289 _, matches = complete(line_buffer="d['")
1344 _, matches = complete(line_buffer="d['")
1290 self.assertIn("my_head", matches)
1345 self.assertIn("my_head", matches)
1291 self.assertIn("my_data", matches)
1346 self.assertIn("my_data", matches)
1347
1292 def completes_on_nested():
1348 def completes_on_nested():
1293 ip.user_ns["d"] = numpy.zeros(2, dtype=dt)
1349 ip.user_ns["d"] = numpy.zeros(2, dtype=dt)
1294 _, matches = complete(line_buffer="d[1]['my_head']['")
1350 _, matches = complete(line_buffer="d[1]['my_head']['")
@@ -1298,10 +1354,10 b' class TestCompleter(unittest.TestCase):'
1298 with greedy_completion():
1354 with greedy_completion():
1299 completes_on_nested()
1355 completes_on_nested()
1300
1356
1301 with evaluation_level('limitted'):
1357 with evaluation_level("limitted"):
1302 completes_on_nested()
1358 completes_on_nested()
1303
1359
1304 with evaluation_level('minimal'):
1360 with evaluation_level("minimal"):
1305 with pytest.raises(AssertionError):
1361 with pytest.raises(AssertionError):
1306 completes_on_nested()
1362 completes_on_nested()
1307
1363
@@ -1653,32 +1709,32 b' class TestCompleter(unittest.TestCase):'
1653
1709
1654
1710
1655 @pytest.mark.parametrize(
1711 @pytest.mark.parametrize(
1656 'input, expected',
1712 "input, expected",
1657 [
1713 [
1658 ['1.234', '1.234'],
1714 ["1.234", "1.234"],
1659 # should match signed numbers
1715 # should match signed numbers
1660 ['+1', '+1'],
1716 ["+1", "+1"],
1661 ['-1', '-1'],
1717 ["-1", "-1"],
1662 ['-1.0', '-1.0'],
1718 ["-1.0", "-1.0"],
1663 ['-1.', '-1.'],
1719 ["-1.", "-1."],
1664 ['+1.', '+1.'],
1720 ["+1.", "+1."],
1665 ['.1', '.1'],
1721 [".1", ".1"],
1666 # should not match non-numbers
1722 # should not match non-numbers
1667 ['1..', None],
1723 ["1..", None],
1668 ['..', None],
1724 ["..", None],
1669 ['.1.', None],
1725 [".1.", None],
1670 # should match after comma
1726 # should match after comma
1671 [',1', '1'],
1727 [",1", "1"],
1672 [', 1', '1'],
1728 [", 1", "1"],
1673 [', .1', '.1'],
1729 [", .1", ".1"],
1674 [', +.1', '+.1'],
1730 [", +.1", "+.1"],
1675 # should not match after trailing spaces
1731 # should not match after trailing spaces
1676 ['.1 ', None],
1732 [".1 ", None],
1677 # some complex cases
1733 # some complex cases
1678 ['0b_0011_1111_0100_1110', '0b_0011_1111_0100_1110'],
1734 ["0b_0011_1111_0100_1110", "0b_0011_1111_0100_1110"],
1679 ['0xdeadbeef', '0xdeadbeef'],
1735 ["0xdeadbeef", "0xdeadbeef"],
1680 ['0b_1110_0101', '0b_1110_0101']
1736 ["0b_1110_0101", "0b_1110_0101"],
1681 ]
1737 ],
1682 )
1738 )
1683 def test_match_numeric_literal_for_dict_key(input, expected):
1739 def test_match_numeric_literal_for_dict_key(input, expected):
1684 assert _match_number_in_dict_key_prefix(input) == expected
1740 assert _match_number_in_dict_key_prefix(input) == expected
@@ -1,53 +1,54 b''
1 from typing import NamedTuple
1 from typing import NamedTuple
2 from IPython.core.guarded_eval import EvaluationContext, GuardRejection, guarded_eval, unbind_method
2 from IPython.core.guarded_eval import (
3 EvaluationContext,
4 GuardRejection,
5 guarded_eval,
6 unbind_method,
7 )
3 from IPython.testing import decorators as dec
8 from IPython.testing import decorators as dec
4 import pytest
9 import pytest
5
10
6
11
7 def limitted(**kwargs):
12 def limitted(**kwargs):
8 return EvaluationContext(
13 return EvaluationContext(locals_=kwargs, globals_={}, evaluation="limitted")
9 locals_=kwargs,
10 globals_={},
11 evaluation='limitted'
12 )
13
14
14
15
15 def unsafe(**kwargs):
16 def unsafe(**kwargs):
16 return EvaluationContext(
17 return EvaluationContext(locals_=kwargs, globals_={}, evaluation="unsafe")
17 locals_=kwargs,
18
18 globals_={},
19 evaluation='unsafe'
20 )
21
19
22 @dec.skip_without('pandas')
20 @dec.skip_without("pandas")
23 def test_pandas_series_iloc():
21 def test_pandas_series_iloc():
24 import pandas as pd
22 import pandas as pd
25 series = pd.Series([1], index=['a'])
23
24 series = pd.Series([1], index=["a"])
26 context = limitted(data=series)
25 context = limitted(data=series)
27 assert guarded_eval('data.iloc[0]', context) == 1
26 assert guarded_eval("data.iloc[0]", context) == 1
28
27
29
28
30 @dec.skip_without('pandas')
29 @dec.skip_without("pandas")
31 def test_pandas_series():
30 def test_pandas_series():
32 import pandas as pd
31 import pandas as pd
33 context = limitted(data=pd.Series([1], index=['a']))
32
33 context = limitted(data=pd.Series([1], index=["a"]))
34 assert guarded_eval('data["a"]', context) == 1
34 assert guarded_eval('data["a"]', context) == 1
35 with pytest.raises(KeyError):
35 with pytest.raises(KeyError):
36 guarded_eval('data["c"]', context)
36 guarded_eval('data["c"]', context)
37
37
38
38
39 @dec.skip_without('pandas')
39 @dec.skip_without("pandas")
40 def test_pandas_bad_series():
40 def test_pandas_bad_series():
41 import pandas as pd
41 import pandas as pd
42
42 class BadItemSeries(pd.Series):
43 class BadItemSeries(pd.Series):
43 def __getitem__(self, key):
44 def __getitem__(self, key):
44 return 'CUSTOM_ITEM'
45 return "CUSTOM_ITEM"
45
46
46 class BadAttrSeries(pd.Series):
47 class BadAttrSeries(pd.Series):
47 def __getattr__(self, key):
48 def __getattr__(self, key):
48 return 'CUSTOM_ATTR'
49 return "CUSTOM_ATTR"
49
50
50 bad_series = BadItemSeries([1], index=['a'])
51 bad_series = BadItemSeries([1], index=["a"])
51 context = limitted(data=bad_series)
52 context = limitted(data=bad_series)
52
53
53 with pytest.raises(GuardRejection):
54 with pytest.raises(GuardRejection):
@@ -58,121 +59,108 b' def test_pandas_bad_series():'
58 # note: here result is a bit unexpected because
59 # note: here result is a bit unexpected because
59 # pandas `__getattr__` calls `__getitem__`;
60 # pandas `__getattr__` calls `__getitem__`;
60 # FIXME - special case to handle it?
61 # FIXME - special case to handle it?
61 assert guarded_eval('data.a', context) == 'CUSTOM_ITEM'
62 assert guarded_eval("data.a", context) == "CUSTOM_ITEM"
62
63
63 context = unsafe(data=bad_series)
64 context = unsafe(data=bad_series)
64 assert guarded_eval('data["a"]', context) == 'CUSTOM_ITEM'
65 assert guarded_eval('data["a"]', context) == "CUSTOM_ITEM"
65
66
66 bad_attr_series = BadAttrSeries([1], index=['a'])
67 bad_attr_series = BadAttrSeries([1], index=["a"])
67 context = limitted(data=bad_attr_series)
68 context = limitted(data=bad_attr_series)
68 assert guarded_eval('data["a"]', context) == 1
69 assert guarded_eval('data["a"]', context) == 1
69 with pytest.raises(GuardRejection):
70 with pytest.raises(GuardRejection):
70 guarded_eval('data.a', context)
71 guarded_eval("data.a", context)
71
72
72
73
73 @dec.skip_without('pandas')
74 @dec.skip_without("pandas")
74 def test_pandas_dataframe_loc():
75 def test_pandas_dataframe_loc():
75 import pandas as pd
76 import pandas as pd
76 from pandas.testing import assert_series_equal
77 from pandas.testing import assert_series_equal
77 data = pd.DataFrame([{'a': 1}])
78
79 data = pd.DataFrame([{"a": 1}])
78 context = limitted(data=data)
80 context = limitted(data=data)
79 assert_series_equal(
81 assert_series_equal(guarded_eval('data.loc[:, "a"]', context), data["a"])
80 guarded_eval('data.loc[:, "a"]', context),
81 data['a']
82 )
83
82
84
83
85 def test_named_tuple():
84 def test_named_tuple():
86
87 class GoodNamedTuple(NamedTuple):
85 class GoodNamedTuple(NamedTuple):
88 a: str
86 a: str
89 pass
87 pass
90
88
91 class BadNamedTuple(NamedTuple):
89 class BadNamedTuple(NamedTuple):
92 a: str
90 a: str
91
93 def __getitem__(self, key):
92 def __getitem__(self, key):
94 return None
93 return None
95
94
96 good = GoodNamedTuple(a='x')
95 good = GoodNamedTuple(a="x")
97 bad = BadNamedTuple(a='x')
96 bad = BadNamedTuple(a="x")
98
97
99 context = limitted(data=good)
98 context = limitted(data=good)
100 assert guarded_eval('data[0]', context) == 'x'
99 assert guarded_eval("data[0]", context) == "x"
101
100
102 context = limitted(data=bad)
101 context = limitted(data=bad)
103 with pytest.raises(GuardRejection):
102 with pytest.raises(GuardRejection):
104 guarded_eval('data[0]', context)
103 guarded_eval("data[0]", context)
105
104
106
105
107 def test_dict():
106 def test_dict():
108 context = limitted(
107 context = limitted(data={"a": 1, "b": {"x": 2}, ("x", "y"): 3})
109 data={'a': 1, 'b': {'x': 2}, ('x', 'y'): 3}
110 )
111 assert guarded_eval('data["a"]', context) == 1
108 assert guarded_eval('data["a"]', context) == 1
112 assert guarded_eval('data["b"]', context) == {'x': 2}
109 assert guarded_eval('data["b"]', context) == {"x": 2}
113 assert guarded_eval('data["b"]["x"]', context) == 2
110 assert guarded_eval('data["b"]["x"]', context) == 2
114 assert guarded_eval('data["x", "y"]', context) == 3
111 assert guarded_eval('data["x", "y"]', context) == 3
115
112
116 assert guarded_eval('data.keys', context)
113 assert guarded_eval("data.keys", context)
117
114
118
115
119 def test_set():
116 def test_set():
120 context = limitted(data={'a', 'b'})
117 context = limitted(data={"a", "b"})
121 assert guarded_eval('data.difference', context)
118 assert guarded_eval("data.difference", context)
122
119
123
120
124 def test_list():
121 def test_list():
125 context = limitted(data=[1, 2, 3])
122 context = limitted(data=[1, 2, 3])
126 assert guarded_eval('data[1]', context) == 2
123 assert guarded_eval("data[1]", context) == 2
127 assert guarded_eval('data.copy', context)
124 assert guarded_eval("data.copy", context)
128
125
129
126
130 def test_dict_literal():
127 def test_dict_literal():
131 context = limitted()
128 context = limitted()
132 assert guarded_eval('{}', context) == {}
129 assert guarded_eval("{}", context) == {}
133 assert guarded_eval('{"a": 1}', context) == {"a": 1}
130 assert guarded_eval('{"a": 1}', context) == {"a": 1}
134
131
135
132
136 def test_list_literal():
133 def test_list_literal():
137 context = limitted()
134 context = limitted()
138 assert guarded_eval('[]', context) == []
135 assert guarded_eval("[]", context) == []
139 assert guarded_eval('[1, "a"]', context) == [1, "a"]
136 assert guarded_eval('[1, "a"]', context) == [1, "a"]
140
137
141
138
142 def test_set_literal():
139 def test_set_literal():
143 context = limitted()
140 context = limitted()
144 assert guarded_eval('set()', context) == set()
141 assert guarded_eval("set()", context) == set()
145 assert guarded_eval('{"a"}', context) == {"a"}
142 assert guarded_eval('{"a"}', context) == {"a"}
146
143
147
144
148 def test_if_expression():
145 def test_if_expression():
149 context = limitted()
146 context = limitted()
150 assert guarded_eval('2 if True else 3', context) == 2
147 assert guarded_eval("2 if True else 3", context) == 2
151 assert guarded_eval('4 if False else 5', context) == 5
148 assert guarded_eval("4 if False else 5", context) == 5
152
149
153
150
154 def test_object():
151 def test_object():
155 obj = object()
152 obj = object()
156 context = limitted(obj=obj)
153 context = limitted(obj=obj)
157 assert guarded_eval('obj.__dir__', context) == obj.__dir__
154 assert guarded_eval("obj.__dir__", context) == obj.__dir__
158
155
159
156
160 @pytest.mark.parametrize(
157 @pytest.mark.parametrize(
161 "code,expected",
158 "code,expected",
162 [
159 [
163 [
160 ["int.numerator", int.numerator],
164 'int.numerator',
161 ["float.is_integer", float.is_integer],
165 int.numerator
162 ["complex.real", complex.real],
166 ],
163 ],
167 [
168 'float.is_integer',
169 float.is_integer
170 ],
171 [
172 'complex.real',
173 complex.real
174 ]
175 ]
176 )
164 )
177 def test_number_attributes(code, expected):
165 def test_number_attributes(code, expected):
178 assert guarded_eval(code, limitted()) == expected
166 assert guarded_eval(code, limitted()) == expected
@@ -180,25 +168,15 b' def test_number_attributes(code, expected):'
180
168
181 def test_method_descriptor():
169 def test_method_descriptor():
182 context = limitted()
170 context = limitted()
183 assert guarded_eval('list.copy.__name__', context) == 'copy'
171 assert guarded_eval("list.copy.__name__", context) == "copy"
184
172
185
173
186 @pytest.mark.parametrize(
174 @pytest.mark.parametrize(
187 "data,good,bad,expected",
175 "data,good,bad,expected",
188 [
176 [
189 [
177 [[1, 2, 3], "data.index(2)", "data.append(4)", 1],
190 [1, 2, 3],
178 [{"a": 1}, "data.keys().isdisjoint({})", "data.update()", True],
191 'data.index(2)',
179 ],
192 'data.append(4)',
193 1
194 ],
195 [
196 {'a': 1},
197 'data.keys().isdisjoint({})',
198 'data.update()',
199 True
200 ]
201 ]
202 )
180 )
203 def test_calls(data, good, bad, expected):
181 def test_calls(data, good, bad, expected):
204 context = limitted(data=data)
182 context = limitted(data=data)
@@ -211,19 +189,10 b' def test_calls(data, good, bad, expected):'
211 @pytest.mark.parametrize(
189 @pytest.mark.parametrize(
212 "code,expected",
190 "code,expected",
213 [
191 [
214 [
192 ["(1\n+\n1)", 2],
215 '(1\n+\n1)',
193 ["list(range(10))[-1:]", [9]],
216 2
194 ["list(range(20))[3:-2:3]", [3, 6, 9, 12, 15]],
217 ],
195 ],
218 [
219 'list(range(10))[-1:]',
220 [9]
221 ],
222 [
223 'list(range(20))[3:-2:3]',
224 [3, 6, 9, 12, 15]
225 ]
226 ]
227 )
196 )
228 def test_literals(code, expected):
197 def test_literals(code, expected):
229 context = limitted()
198 context = limitted()
@@ -232,22 +201,20 b' def test_literals(code, expected):'
232
201
233 def test_subscript():
202 def test_subscript():
234 context = EvaluationContext(
203 context = EvaluationContext(
235 locals_={},
204 locals_={}, globals_={}, evaluation="limitted", in_subscript=True
236 globals_={},
237 evaluation='limitted',
238 in_subscript=True
239 )
205 )
240 empty_slice = slice(None, None, None)
206 empty_slice = slice(None, None, None)
241 assert guarded_eval('', context) == tuple()
207 assert guarded_eval("", context) == tuple()
242 assert guarded_eval(':', context) == empty_slice
208 assert guarded_eval(":", context) == empty_slice
243 assert guarded_eval('1:2:3', context) == slice(1, 2, 3)
209 assert guarded_eval("1:2:3", context) == slice(1, 2, 3)
244 assert guarded_eval(':, "a"', context) == (empty_slice, "a")
210 assert guarded_eval(':, "a"', context) == (empty_slice, "a")
245
211
246
212
247 def test_unbind_method():
213 def test_unbind_method():
248 class X(list):
214 class X(list):
249 def index(self, k):
215 def index(self, k):
250 return 'CUSTOM'
216 return "CUSTOM"
217
251 x = X()
218 x = X()
252 assert unbind_method(x.index) is X.index
219 assert unbind_method(x.index) is X.index
253 assert unbind_method([].index) is list.index
220 assert unbind_method([].index) is list.index
@@ -261,16 +228,19 b' def test_assumption_instance_attr_do_not_matter():'
261 versions could invalidate this assumptions. This test
228 versions could invalidate this assumptions. This test
262 is meant to catch such a change if it ever comes true.
229 is meant to catch such a change if it ever comes true.
263 """
230 """
231
264 class T:
232 class T:
265 def __getitem__(self, k):
233 def __getitem__(self, k):
266 return 'a'
234 return "a"
235
267 def __getattr__(self, k):
236 def __getattr__(self, k):
268 return 'a'
237 return "a"
238
269 t = T()
239 t = T()
270 t.__getitem__ = lambda f: 'b'
240 t.__getitem__ = lambda f: "b"
271 t.__getattr__ = lambda f: 'b'
241 t.__getattr__ = lambda f: "b"
272 assert t[1] == 'a'
242 assert t[1] == "a"
273 assert t[1] == 'a'
243 assert t[1] == "a"
274
244
275
245
276 def test_assumption_named_tuples_share_getitem():
246 def test_assumption_named_tuples_share_getitem():
General Comments 0
You need to be logged in to leave comments. Login now