Show More
@@ -50,7 +50,7 b' Backward latex completion' | |||||
50 |
|
50 | |||
51 | It is sometime challenging to know how to type a character, if you are using |
|
51 | It is sometime challenging to know how to type a character, if you are using | |
52 | IPython, or any compatible frontend you can prepend backslash to the character |
|
52 | IPython, or any compatible frontend you can prepend backslash to the character | |
53 |
and press |
|
53 | and press :kbd:`Tab` to expand it to its latex form. | |
54 |
|
54 | |||
55 | .. code:: |
|
55 | .. code:: | |
56 |
|
56 | |||
@@ -59,7 +59,7 b' and press ``<tab>`` to expand it to its latex form.' | |||||
59 |
|
59 | |||
60 |
|
60 | |||
61 | Both forward and backward completions can be deactivated by setting the |
|
61 | Both forward and backward completions can be deactivated by setting the | |
62 |
|
|
62 | :any:`Completer.backslash_combining_completions` option to ``False``. | |
63 |
|
63 | |||
64 |
|
64 | |||
65 | Experimental |
|
65 | Experimental | |
@@ -95,7 +95,7 b' having to execute any code:' | |||||
95 | ... myvar[1].bi<tab> |
|
95 | ... myvar[1].bi<tab> | |
96 |
|
96 | |||
97 | Tab completion will be able to infer that ``myvar[1]`` is a real number without |
|
97 | Tab completion will be able to infer that ``myvar[1]`` is a real number without | |
98 |
executing any code unlike the |
|
98 | executing almost any code unlike the deprecated :any:`IPCompleter.greedy` | |
99 | option. |
|
99 | option. | |
100 |
|
100 | |||
101 | Be sure to update :any:`jedi` to the latest stable version or to try the |
|
101 | Be sure to update :any:`jedi` to the latest stable version or to try the | |
@@ -972,29 +972,38 b' class Completer(Configurable):' | |||||
972 | help="""Activate greedy completion. |
|
972 | help="""Activate greedy completion. | |
973 |
|
973 | |||
974 | .. deprecated:: 8.8 |
|
974 | .. deprecated:: 8.8 | |
975 | Use :any:`evaluation` and :any:`auto_close_dict_keys` instead. |
|
975 | Use :any:`Completer.evaluation` and :any:`Completer.auto_close_dict_keys` instead. | |
976 |
|
976 | |||
977 | When enabled in IPython 8.8+ activates following settings for compatibility: |
|
977 | When enabled in IPython 8.8 or newer, changes configuration as follows: | |
978 | - ``evaluation = 'unsafe'`` |
|
978 | ||
979 | - ``auto_close_dict_keys = True`` |
|
979 | - ``Completer.evaluation = 'unsafe'`` | |
|
980 | - ``Completer.auto_close_dict_keys = True`` | |||
980 | """, |
|
981 | """, | |
981 | ).tag(config=True) |
|
982 | ).tag(config=True) | |
982 |
|
983 | |||
983 | evaluation = Enum( |
|
984 | evaluation = Enum( | |
984 | ("forbidden", "minimal", "limited", "unsafe", "dangerous"), |
|
985 | ("forbidden", "minimal", "limited", "unsafe", "dangerous"), | |
985 | default_value="limited", |
|
986 | default_value="limited", | |
986 |
help=""" |
|
987 | help="""Policy for code evaluation under completion. | |
987 |
|
988 | |||
988 |
Successive options allow to enable more eager evaluation for |
|
989 | Successive options allow to enable more eager evaluation for better | |
989 | including for nested dictionaries, nested lists, or even results of function calls. Setting `unsafe` |
|
990 | completion suggestions, including for nested dictionaries, nested lists, | |
990 | or higher can lead to evaluation of arbitrary user code on TAB with potentially dangerous side effects. |
|
991 | or even results of function calls. | |
|
992 | Setting ``unsafe`` or higher can lead to evaluation of arbitrary user | |||
|
993 | code on :kbd:`Tab` with potentially unwanted or dangerous side effects. | |||
991 |
|
994 | |||
992 | Allowed values are: |
|
995 | Allowed values are: | |
993 | - `forbidden`: no evaluation at all |
|
996 | ||
994 | - `minimal`: evaluation of literals and access to built-in namespaces; no item/attribute evaluation nor access to locals/globals |
|
997 | - ``forbidden``: no evaluation of code is permitted, | |
995 | - `limited` (default): access to all namespaces, evaluation of hard-coded methods (``keys()``, ``__getattr__``, ``__getitems__``, etc) on allow-listed objects (e.g. ``dict``, ``list``, ``tuple``, ``pandas.Series``) |
|
998 | - ``minimal``: evaluation of literals and access to built-in namespace; | |
996 | - `unsafe`: evaluation of all methods and function calls but not of syntax with side-effects like `del x`, |
|
999 | no item/attribute evaluation nor access to locals/globals, | |
997 | - `dangerous`: completely arbitrary evaluation |
|
1000 | - ``limited``: access to all namespaces, evaluation of hard-coded methods | |
|
1001 | (for example: :any:`dict.keys`, :any:`object.__getattr__`, | |||
|
1002 | :any:`object.__getitem__`) on allow-listed objects (for example: | |||
|
1003 | :any:`dict`, :any:`list`, :any:`tuple`, ``pandas.Series``), | |||
|
1004 | - ``unsafe``: evaluation of all methods and function calls but not of | |||
|
1005 | syntax with side-effects like `del x`, | |||
|
1006 | - ``dangerous``: completely arbitrary evaluation. | |||
998 | """, |
|
1007 | """, | |
999 | ).tag(config=True) |
|
1008 | ).tag(config=True) | |
1000 |
|
1009 | |||
@@ -1019,7 +1028,15 b' class Completer(Configurable):' | |||||
1019 | "unicode characters back to latex commands.").tag(config=True) |
|
1028 | "unicode characters back to latex commands.").tag(config=True) | |
1020 |
|
1029 | |||
1021 | auto_close_dict_keys = Bool( |
|
1030 | auto_close_dict_keys = Bool( | |
1022 | False, help="""Enable auto-closing dictionary keys.""" |
|
1031 | False, | |
|
1032 | help=""" | |||
|
1033 | Enable auto-closing dictionary keys. | |||
|
1034 | ||||
|
1035 | When enabled string keys will be suffixed with a final quote | |||
|
1036 | (matching the opening quote), tuple keys will also receive a | |||
|
1037 | separating comma if needed, and keys which are final will | |||
|
1038 | receive a closing bracket (``]``). | |||
|
1039 | """, | |||
1023 | ).tag(config=True) |
|
1040 | ).tag(config=True) | |
1024 |
|
1041 | |||
1025 | def __init__(self, namespace=None, global_namespace=None, **kwargs): |
|
1042 | def __init__(self, namespace=None, global_namespace=None, **kwargs): | |
@@ -1157,8 +1174,8 b' class Completer(Configurable):' | |||||
1157 | obj = guarded_eval( |
|
1174 | obj = guarded_eval( | |
1158 | expr, |
|
1175 | expr, | |
1159 | EvaluationContext( |
|
1176 | EvaluationContext( | |
1160 |
globals |
|
1177 | globals=self.global_namespace, | |
1161 |
locals |
|
1178 | locals=self.namespace, | |
1162 | evaluation=self.evaluation, |
|
1179 | evaluation=self.evaluation, | |
1163 | ), |
|
1180 | ), | |
1164 | ) |
|
1181 | ) | |
@@ -1183,7 +1200,7 b' def get__all__entries(obj):' | |||||
1183 | return [w for w in words if isinstance(w, str)] |
|
1200 | return [w for w in words if isinstance(w, str)] | |
1184 |
|
1201 | |||
1185 |
|
1202 | |||
1186 | class DictKeyState(enum.Flag): |
|
1203 | class _DictKeyState(enum.Flag): | |
1187 | """Represent state of the key match in context of other possible matches. |
|
1204 | """Represent state of the key match in context of other possible matches. | |
1188 |
|
1205 | |||
1189 | - given `d1 = {'a': 1}` completion on `d1['<tab>` will yield `{'a': END_OF_ITEM}` as there is no tuple. |
|
1206 | - given `d1 = {'a': 1}` completion on `d1['<tab>` will yield `{'a': END_OF_ITEM}` as there is no tuple. | |
@@ -1199,6 +1216,7 b' class DictKeyState(enum.Flag):' | |||||
1199 |
|
1216 | |||
1200 |
|
1217 | |||
1201 | def _parse_tokens(c): |
|
1218 | def _parse_tokens(c): | |
|
1219 | """Parse tokens even if there is an error.""" | |||
1202 | tokens = [] |
|
1220 | tokens = [] | |
1203 | token_generator = tokenize.generate_tokens(iter(c.splitlines()).__next__) |
|
1221 | token_generator = tokenize.generate_tokens(iter(c.splitlines()).__next__) | |
1204 | while True: |
|
1222 | while True: | |
@@ -1257,7 +1275,7 b' def match_dict_keys(' | |||||
1257 | prefix: str, |
|
1275 | prefix: str, | |
1258 | delims: str, |
|
1276 | delims: str, | |
1259 | extra_prefix: Optional[Tuple[Union[str, bytes], ...]] = None, |
|
1277 | extra_prefix: Optional[Tuple[Union[str, bytes], ...]] = None, | |
1260 | ) -> Tuple[str, int, Dict[str, DictKeyState]]: |
|
1278 | ) -> Tuple[str, int, Dict[str, _DictKeyState]]: | |
1261 | """Used by dict_key_matches, matching the prefix to a list of keys |
|
1279 | """Used by dict_key_matches, matching the prefix to a list of keys | |
1262 |
|
1280 | |||
1263 | Parameters |
|
1281 | Parameters | |
@@ -1307,8 +1325,8 b' def match_dict_keys(' | |||||
1307 | return True |
|
1325 | return True | |
1308 |
|
1326 | |||
1309 | filtered_key_is_final: Dict[ |
|
1327 | filtered_key_is_final: Dict[ | |
1310 | Union[str, bytes, int, float], DictKeyState |
|
1328 | Union[str, bytes, int, float], _DictKeyState | |
1311 | ] = defaultdict(lambda: DictKeyState.BASELINE) |
|
1329 | ] = defaultdict(lambda: _DictKeyState.BASELINE) | |
1312 |
|
1330 | |||
1313 | for k in keys: |
|
1331 | for k in keys: | |
1314 | # If at least one of the matches is not final, mark as undetermined. |
|
1332 | # If at least one of the matches is not final, mark as undetermined. | |
@@ -1319,9 +1337,9 b' def match_dict_keys(' | |||||
1319 | if filter_prefix_tuple(k): |
|
1337 | if filter_prefix_tuple(k): | |
1320 | key_fragment = k[prefix_tuple_size] |
|
1338 | key_fragment = k[prefix_tuple_size] | |
1321 | filtered_key_is_final[key_fragment] |= ( |
|
1339 | filtered_key_is_final[key_fragment] |= ( | |
1322 | DictKeyState.END_OF_TUPLE |
|
1340 | _DictKeyState.END_OF_TUPLE | |
1323 | if len(k) == prefix_tuple_size + 1 |
|
1341 | if len(k) == prefix_tuple_size + 1 | |
1324 | else DictKeyState.IN_TUPLE |
|
1342 | else _DictKeyState.IN_TUPLE | |
1325 | ) |
|
1343 | ) | |
1326 | elif prefix_tuple_size > 0: |
|
1344 | elif prefix_tuple_size > 0: | |
1327 | # we are completing a tuple but this key is not a tuple, |
|
1345 | # we are completing a tuple but this key is not a tuple, | |
@@ -1329,7 +1347,7 b' def match_dict_keys(' | |||||
1329 | pass |
|
1347 | pass | |
1330 | else: |
|
1348 | else: | |
1331 | if isinstance(k, text_serializable_types): |
|
1349 | if isinstance(k, text_serializable_types): | |
1332 | filtered_key_is_final[k] |= DictKeyState.END_OF_ITEM |
|
1350 | filtered_key_is_final[k] |= _DictKeyState.END_OF_ITEM | |
1333 |
|
1351 | |||
1334 | filtered_keys = filtered_key_is_final.keys() |
|
1352 | filtered_keys = filtered_key_is_final.keys() | |
1335 |
|
1353 | |||
@@ -1367,7 +1385,7 b' def match_dict_keys(' | |||||
1367 | token_start = token_match.start() |
|
1385 | token_start = token_match.start() | |
1368 | token_prefix = token_match.group() |
|
1386 | token_prefix = token_match.group() | |
1369 |
|
1387 | |||
1370 | matched: Dict[str, DictKeyState] = {} |
|
1388 | matched: Dict[str, _DictKeyState] = {} | |
1371 |
|
1389 | |||
1372 | str_key: Union[str, bytes] |
|
1390 | str_key: Union[str, bytes] | |
1373 |
|
1391 | |||
@@ -2503,8 +2521,8 b' class IPCompleter(Completer):' | |||||
2503 | tuple_prefix = guarded_eval( |
|
2521 | tuple_prefix = guarded_eval( | |
2504 | prior_tuple_keys, |
|
2522 | prior_tuple_keys, | |
2505 | EvaluationContext( |
|
2523 | EvaluationContext( | |
2506 |
globals |
|
2524 | globals=self.global_namespace, | |
2507 |
locals |
|
2525 | locals=self.namespace, | |
2508 | evaluation=self.evaluation, |
|
2526 | evaluation=self.evaluation, | |
2509 | in_subscript=True, |
|
2527 | in_subscript=True, | |
2510 | ), |
|
2528 | ), | |
@@ -2569,7 +2587,7 b' class IPCompleter(Completer):' | |||||
2569 |
|
2587 | |||
2570 | results = [] |
|
2588 | results = [] | |
2571 |
|
2589 | |||
2572 | end_of_tuple_or_item = DictKeyState.END_OF_TUPLE | DictKeyState.END_OF_ITEM |
|
2590 | end_of_tuple_or_item = _DictKeyState.END_OF_TUPLE | _DictKeyState.END_OF_ITEM | |
2573 |
|
2591 | |||
2574 | for k, state_flag in matches.items(): |
|
2592 | for k, state_flag in matches.items(): | |
2575 | result = leading + k |
|
2593 | result = leading + k | |
@@ -2584,7 +2602,7 b' class IPCompleter(Completer):' | |||||
2584 |
|
2602 | |||
2585 | if state_flag in end_of_tuple_or_item and can_close_bracket: |
|
2603 | if state_flag in end_of_tuple_or_item and can_close_bracket: | |
2586 | result += "]" |
|
2604 | result += "]" | |
2587 | if state_flag == DictKeyState.IN_TUPLE and can_close_tuple_item: |
|
2605 | if state_flag == _DictKeyState.IN_TUPLE and can_close_tuple_item: | |
2588 | result += ", " |
|
2606 | result += ", " | |
2589 | results.append(result) |
|
2607 | results.append(result) | |
2590 | return results |
|
2608 | return results |
@@ -17,6 +17,7 b' from functools import cached_property' | |||||
17 | from dataclasses import dataclass, field |
|
17 | from dataclasses import dataclass, field | |
18 |
|
18 | |||
19 | from IPython.utils.docs import GENERATING_DOCUMENTATION |
|
19 | from IPython.utils.docs import GENERATING_DOCUMENTATION | |
|
20 | from IPython.utils.decorators import undoc | |||
20 |
|
21 | |||
21 |
|
22 | |||
22 | if TYPE_CHECKING or GENERATING_DOCUMENTATION: |
|
23 | if TYPE_CHECKING or GENERATING_DOCUMENTATION: | |
@@ -26,21 +27,25 b' else:' | |||||
26 | Protocol = object # requires Python >=3.8 |
|
27 | Protocol = object # requires Python >=3.8 | |
27 |
|
28 | |||
28 |
|
29 | |||
|
30 | @undoc | |||
29 | class HasGetItem(Protocol): |
|
31 | class HasGetItem(Protocol): | |
30 | def __getitem__(self, key) -> None: |
|
32 | def __getitem__(self, key) -> None: | |
31 | ... |
|
33 | ... | |
32 |
|
34 | |||
33 |
|
35 | |||
|
36 | @undoc | |||
34 | class InstancesHaveGetItem(Protocol): |
|
37 | class InstancesHaveGetItem(Protocol): | |
35 | def __call__(self, *args, **kwargs) -> HasGetItem: |
|
38 | def __call__(self, *args, **kwargs) -> HasGetItem: | |
36 | ... |
|
39 | ... | |
37 |
|
40 | |||
38 |
|
41 | |||
|
42 | @undoc | |||
39 | class HasGetAttr(Protocol): |
|
43 | class HasGetAttr(Protocol): | |
40 | def __getattr__(self, key) -> None: |
|
44 | def __getattr__(self, key) -> None: | |
41 | ... |
|
45 | ... | |
42 |
|
46 | |||
43 |
|
47 | |||
|
48 | @undoc | |||
44 | class DoesNotHaveGetAttr(Protocol): |
|
49 | class DoesNotHaveGetAttr(Protocol): | |
45 | pass |
|
50 | pass | |
46 |
|
51 | |||
@@ -49,7 +54,7 b' class DoesNotHaveGetAttr(Protocol):' | |||||
49 | MayHaveGetattr = Union[HasGetAttr, DoesNotHaveGetAttr] |
|
54 | MayHaveGetattr = Union[HasGetAttr, DoesNotHaveGetAttr] | |
50 |
|
55 | |||
51 |
|
56 | |||
52 | def unbind_method(func: Callable) -> Union[Callable, None]: |
|
57 | def _unbind_method(func: Callable) -> Union[Callable, None]: | |
53 | """Get unbound method for given bound method. |
|
58 | """Get unbound method for given bound method. | |
54 |
|
59 | |||
55 | Returns None if cannot get unbound method.""" |
|
60 | Returns None if cannot get unbound method.""" | |
@@ -69,8 +74,11 b' def unbind_method(func: Callable) -> Union[Callable, None]:' | |||||
69 | return None |
|
74 | return None | |
70 |
|
75 | |||
71 |
|
76 | |||
|
77 | @undoc | |||
72 | @dataclass |
|
78 | @dataclass | |
73 | class EvaluationPolicy: |
|
79 | class EvaluationPolicy: | |
|
80 | """Definition of evaluation policy.""" | |||
|
81 | ||||
74 | allow_locals_access: bool = False |
|
82 | allow_locals_access: bool = False | |
75 | allow_globals_access: bool = False |
|
83 | allow_globals_access: bool = False | |
76 | allow_item_access: bool = False |
|
84 | allow_item_access: bool = False | |
@@ -92,12 +100,12 b' class EvaluationPolicy:' | |||||
92 | if func in self.allowed_calls: |
|
100 | if func in self.allowed_calls: | |
93 | return True |
|
101 | return True | |
94 |
|
102 | |||
95 | owner_method = unbind_method(func) |
|
103 | owner_method = _unbind_method(func) | |
96 | if owner_method and owner_method in self.allowed_calls: |
|
104 | if owner_method and owner_method in self.allowed_calls: | |
97 | return True |
|
105 | return True | |
98 |
|
106 | |||
99 |
|
107 | |||
100 | def has_original_dunder_external( |
|
108 | def _has_original_dunder_external( | |
101 | value, |
|
109 | value, | |
102 | module_name, |
|
110 | module_name, | |
103 | access_path, |
|
111 | access_path, | |
@@ -121,7 +129,7 b' def has_original_dunder_external(' | |||||
121 | return False |
|
129 | return False | |
122 |
|
130 | |||
123 |
|
131 | |||
124 | def has_original_dunder( |
|
132 | def _has_original_dunder( | |
125 | value, allowed_types, allowed_methods, allowed_external, method_name |
|
133 | value, allowed_types, allowed_methods, allowed_external, method_name | |
126 | ): |
|
134 | ): | |
127 | # note: Python ignores `__getattr__`/`__getitem__` on instances, |
|
135 | # note: Python ignores `__getattr__`/`__getitem__` on instances, | |
@@ -141,12 +149,13 b' def has_original_dunder(' | |||||
141 | return True |
|
149 | return True | |
142 |
|
150 | |||
143 | for module_name, *access_path in allowed_external: |
|
151 | for module_name, *access_path in allowed_external: | |
144 | if has_original_dunder_external(value, module_name, access_path, method_name): |
|
152 | if _has_original_dunder_external(value, module_name, access_path, method_name): | |
145 | return True |
|
153 | return True | |
146 |
|
154 | |||
147 | return False |
|
155 | return False | |
148 |
|
156 | |||
149 |
|
157 | |||
|
158 | @undoc | |||
150 | @dataclass |
|
159 | @dataclass | |
151 | class SelectivePolicy(EvaluationPolicy): |
|
160 | class SelectivePolicy(EvaluationPolicy): | |
152 | allowed_getitem: Set[InstancesHaveGetItem] = field(default_factory=set) |
|
161 | allowed_getitem: Set[InstancesHaveGetItem] = field(default_factory=set) | |
@@ -155,14 +164,14 b' class SelectivePolicy(EvaluationPolicy):' | |||||
155 | allowed_getattr_external: Set[Tuple[str, ...]] = field(default_factory=set) |
|
164 | allowed_getattr_external: Set[Tuple[str, ...]] = field(default_factory=set) | |
156 |
|
165 | |||
157 | def can_get_attr(self, value, attr): |
|
166 | def can_get_attr(self, value, attr): | |
158 | has_original_attribute = has_original_dunder( |
|
167 | has_original_attribute = _has_original_dunder( | |
159 | value, |
|
168 | value, | |
160 | allowed_types=self.allowed_getattr, |
|
169 | allowed_types=self.allowed_getattr, | |
161 | allowed_methods=self._getattribute_methods, |
|
170 | allowed_methods=self._getattribute_methods, | |
162 | allowed_external=self.allowed_getattr_external, |
|
171 | allowed_external=self.allowed_getattr_external, | |
163 | method_name="__getattribute__", |
|
172 | method_name="__getattribute__", | |
164 | ) |
|
173 | ) | |
165 | has_original_attr = has_original_dunder( |
|
174 | has_original_attr = _has_original_dunder( | |
166 | value, |
|
175 | value, | |
167 | allowed_types=self.allowed_getattr, |
|
176 | allowed_types=self.allowed_getattr, | |
168 | allowed_methods=self._getattr_methods, |
|
177 | allowed_methods=self._getattr_methods, | |
@@ -182,7 +191,7 b' class SelectivePolicy(EvaluationPolicy):' | |||||
182 |
|
191 | |||
183 | def can_get_item(self, value, item): |
|
192 | def can_get_item(self, value, item): | |
184 | """Allow accessing `__getiitem__` of allow-listed instances unless it was not modified.""" |
|
193 | """Allow accessing `__getiitem__` of allow-listed instances unless it was not modified.""" | |
185 | return has_original_dunder( |
|
194 | return _has_original_dunder( | |
186 | value, |
|
195 | value, | |
187 | allowed_types=self.allowed_getitem, |
|
196 | allowed_types=self.allowed_getitem, | |
188 | allowed_methods=self._getitem_methods, |
|
197 | allowed_methods=self._getitem_methods, | |
@@ -211,34 +220,50 b' class SelectivePolicy(EvaluationPolicy):' | |||||
211 | } |
|
220 | } | |
212 |
|
221 | |||
213 |
|
222 | |||
214 | class DummyNamedTuple(NamedTuple): |
|
223 | class _DummyNamedTuple(NamedTuple): | |
215 | pass |
|
224 | pass | |
216 |
|
225 | |||
217 |
|
226 | |||
218 | class EvaluationContext(NamedTuple): |
|
227 | class EvaluationContext(NamedTuple): | |
219 | locals_: dict |
|
228 | #: Local namespace | |
220 |
|
|
229 | locals: dict | |
|
230 | #: Global namespace | |||
|
231 | globals: dict | |||
|
232 | #: Evaluation policy identifier | |||
221 | evaluation: Literal[ |
|
233 | evaluation: Literal[ | |
222 | "forbidden", "minimal", "limited", "unsafe", "dangerous" |
|
234 | "forbidden", "minimal", "limited", "unsafe", "dangerous" | |
223 | ] = "forbidden" |
|
235 | ] = "forbidden" | |
|
236 | #: Whether the evalution of code takes place inside of a subscript. | |||
|
237 | #: Useful for evaluating ``:-1, 'col'`` in ``df[:-1, 'col']``. | |||
224 | in_subscript: bool = False |
|
238 | in_subscript: bool = False | |
225 |
|
239 | |||
226 |
|
240 | |||
227 | class IdentitySubscript: |
|
241 | class _IdentitySubscript: | |
|
242 | """Returns the key itself when item is requested via subscript.""" | |||
|
243 | ||||
228 | def __getitem__(self, key): |
|
244 | def __getitem__(self, key): | |
229 | return key |
|
245 | return key | |
230 |
|
246 | |||
231 |
|
247 | |||
232 | IDENTITY_SUBSCRIPT = IdentitySubscript() |
|
248 | IDENTITY_SUBSCRIPT = _IdentitySubscript() | |
233 | SUBSCRIPT_MARKER = "__SUBSCRIPT_SENTINEL__" |
|
249 | SUBSCRIPT_MARKER = "__SUBSCRIPT_SENTINEL__" | |
234 |
|
250 | |||
235 |
|
251 | |||
236 |
class GuardRejection( |
|
252 | class GuardRejection(Exception): | |
|
253 | """Exception raised when guard rejects evaluation attempt.""" | |||
|
254 | ||||
237 | pass |
|
255 | pass | |
238 |
|
256 | |||
239 |
|
257 | |||
240 | def guarded_eval(code: str, context: EvaluationContext): |
|
258 | def guarded_eval(code: str, context: EvaluationContext): | |
241 | locals_ = context.locals_ |
|
259 | """Evaluate provided code in the evaluation context. | |
|
260 | ||||
|
261 | If evaluation policy given by context is set to ``forbidden`` | |||
|
262 | no evaluation will be performed; if it is set to ``dangerous`` | |||
|
263 | standard :func:`eval` will be used; finally, for any other, | |||
|
264 | policy :func:`eval_node` will be called on parsed AST. | |||
|
265 | """ | |||
|
266 | locals_ = context.locals | |||
242 |
|
267 | |||
243 | if context.evaluation == "forbidden": |
|
268 | if context.evaluation == "forbidden": | |
244 | raise GuardRejection("Forbidden mode") |
|
269 | raise GuardRejection("Forbidden mode") | |
@@ -256,10 +281,10 b' def guarded_eval(code: str, context: EvaluationContext):' | |||||
256 | locals_ = locals_.copy() |
|
281 | locals_ = locals_.copy() | |
257 | locals_[SUBSCRIPT_MARKER] = IDENTITY_SUBSCRIPT |
|
282 | locals_[SUBSCRIPT_MARKER] = IDENTITY_SUBSCRIPT | |
258 | code = SUBSCRIPT_MARKER + "[" + code + "]" |
|
283 | code = SUBSCRIPT_MARKER + "[" + code + "]" | |
259 |
context = EvaluationContext(**{**context._asdict(), **{"locals |
|
284 | context = EvaluationContext(**{**context._asdict(), **{"locals": locals_}}) | |
260 |
|
285 | |||
261 | if context.evaluation == "dangerous": |
|
286 | if context.evaluation == "dangerous": | |
262 |
return eval(code, context.globals |
|
287 | return eval(code, context.globals, context.locals) | |
263 |
|
288 | |||
264 | expression = ast.parse(code, mode="eval") |
|
289 | expression = ast.parse(code, mode="eval") | |
265 |
|
290 | |||
@@ -267,14 +292,12 b' def guarded_eval(code: str, context: EvaluationContext):' | |||||
267 |
|
292 | |||
268 |
|
293 | |||
269 | def eval_node(node: Union[ast.AST, None], context: EvaluationContext): |
|
294 | def eval_node(node: Union[ast.AST, None], context: EvaluationContext): | |
270 | """ |
|
295 | """Evaluate AST node in provided context. | |
271 | Evaluate AST node in provided context. |
|
|||
272 |
|
296 | |||
273 | Applies evaluation restrictions defined in the context. |
|
297 | Applies evaluation restrictions defined in the context. Currently does not support evaluation of functions with keyword arguments. | |
274 |
|
298 | |||
275 | Currently does not support evaluation of functions with keyword arguments. |
|
299 | Does not evaluate actions that always have side effects: | |
276 |
|
300 | |||
277 | Does not evaluate actions which always have side effects: |
|
|||
278 | - class definitions (``class sth: ...``) |
|
301 | - class definitions (``class sth: ...``) | |
279 | - function definitions (``def sth: ...``) |
|
302 | - function definitions (``def sth: ...``) | |
280 | - variable assignments (``x = 1``) |
|
303 | - variable assignments (``x = 1``) | |
@@ -282,10 +305,12 b' def eval_node(node: Union[ast.AST, None], context: EvaluationContext):' | |||||
282 | - deletions (``del x``) |
|
305 | - deletions (``del x``) | |
283 |
|
306 | |||
284 | Does not evaluate operations which do not return values: |
|
307 | Does not evaluate operations which do not return values: | |
|
308 | ||||
285 | - assertions (``assert x``) |
|
309 | - assertions (``assert x``) | |
286 | - pass (``pass``) |
|
310 | - pass (``pass``) | |
287 | - imports (``import x``) |
|
311 | - imports (``import x``) | |
288 | - control flow |
|
312 | - control flow: | |
|
313 | ||||
289 | - conditionals (``if x:``) except for ternary IfExp (``a if x else b``) |
|
314 | - conditionals (``if x:``) except for ternary IfExp (``a if x else b``) | |
290 | - loops (``for`` and `while``) |
|
315 | - loops (``for`` and `while``) | |
291 | - exception handling |
|
316 | - exception handling | |
@@ -376,10 +401,10 b' def eval_node(node: Union[ast.AST, None], context: EvaluationContext):' | |||||
376 | f" not allowed in {context.evaluation} mode", |
|
401 | f" not allowed in {context.evaluation} mode", | |
377 | ) |
|
402 | ) | |
378 | if isinstance(node, ast.Name): |
|
403 | if isinstance(node, ast.Name): | |
379 |
if policy.allow_locals_access and node.id in context.locals |
|
404 | if policy.allow_locals_access and node.id in context.locals: | |
380 |
return context.locals |
|
405 | return context.locals[node.id] | |
381 |
if policy.allow_globals_access and node.id in context.globals |
|
406 | if policy.allow_globals_access and node.id in context.globals: | |
382 |
return context.globals |
|
407 | return context.globals[node.id] | |
383 | if policy.allow_builtins_access and hasattr(builtins, node.id): |
|
408 | if policy.allow_builtins_access and hasattr(builtins, node.id): | |
384 | # note: do not use __builtins__, it is implementation detail of Python |
|
409 | # note: do not use __builtins__, it is implementation detail of Python | |
385 | return getattr(builtins, node.id) |
|
410 | return getattr(builtins, node.id) | |
@@ -439,8 +464,8 b' BUILTIN_GETITEM: Set[InstancesHaveGetItem] = {' | |||||
439 | collections.UserDict, |
|
464 | collections.UserDict, | |
440 | collections.UserList, |
|
465 | collections.UserList, | |
441 | collections.UserString, |
|
466 | collections.UserString, | |
442 | DummyNamedTuple, |
|
467 | _DummyNamedTuple, | |
443 | IdentitySubscript, |
|
468 | _IdentitySubscript, | |
444 | } |
|
469 | } | |
445 |
|
470 | |||
446 |
|
471 | |||
@@ -537,3 +562,12 b' EVALUATION_POLICIES = {' | |||||
537 | allow_any_calls=True, |
|
562 | allow_any_calls=True, | |
538 | ), |
|
563 | ), | |
539 | } |
|
564 | } | |
|
565 | ||||
|
566 | ||||
|
567 | __all__ = [ | |||
|
568 | "guarded_eval", | |||
|
569 | "eval_node", | |||
|
570 | "GuardRejection", | |||
|
571 | "EvaluationContext", | |||
|
572 | "_unbind_method", | |||
|
573 | ] |
@@ -68,94 +68,22 b' class ConfigMagics(Magics):' | |||||
68 | To view what is configurable on a given class, just pass the class |
|
68 | To view what is configurable on a given class, just pass the class | |
69 | name:: |
|
69 | name:: | |
70 |
|
70 | |||
71 |
In [2]: %config |
|
71 | In [2]: %config LoggingMagics | |
72 | IPCompleter(Completer) options |
|
72 | LoggingMagics(Magics) options | |
73 |
--------------------------- |
|
73 | --------------------------- | |
74 | IPCompleter.backslash_combining_completions=<Bool> |
|
74 | LoggingMagics.quiet=<Bool> | |
75 | Enable unicode completions, e.g. \\alpha<tab> . Includes completion of latex |
|
75 | Suppress output of log state when logging is enabled | |
76 | commands, unicode names, and expanding unicode characters back to latex |
|
|||
77 | commands. |
|
|||
78 | Current: True |
|
|||
79 | IPCompleter.debug=<Bool> |
|
|||
80 | Enable debug for the Completer. Mostly print extra information for |
|
|||
81 | experimental jedi integration. |
|
|||
82 | Current: False |
|
76 | Current: False | |
83 | IPCompleter.disable_matchers=<list-item-1>... |
|
|||
84 | List of matchers to disable. |
|
|||
85 | The list should contain matcher identifiers (see |
|
|||
86 | :any:`completion_matcher`). |
|
|||
87 | Current: [] |
|
|||
88 | IPCompleter.greedy=<Bool> |
|
|||
89 | Activate greedy completion |
|
|||
90 | PENDING DEPRECATION. this is now mostly taken care of with Jedi. |
|
|||
91 | This will enable completion on elements of lists, results of function calls, etc., |
|
|||
92 | but can be unsafe because the code is actually evaluated on TAB. |
|
|||
93 | Current: False |
|
|||
94 | IPCompleter.jedi_compute_type_timeout=<Int> |
|
|||
95 | Experimental: restrict time (in milliseconds) during which Jedi can compute types. |
|
|||
96 | Set to 0 to stop computing types. Non-zero value lower than 100ms may hurt |
|
|||
97 | performance by preventing jedi to build its cache. |
|
|||
98 | Current: 400 |
|
|||
99 | IPCompleter.limit_to__all__=<Bool> |
|
|||
100 | DEPRECATED as of version 5.0. |
|
|||
101 | Instruct the completer to use __all__ for the completion |
|
|||
102 | Specifically, when completing on ``object.<tab>``. |
|
|||
103 | When True: only those names in obj.__all__ will be included. |
|
|||
104 | When False [default]: the __all__ attribute is ignored |
|
|||
105 | Current: False |
|
|||
106 | IPCompleter.merge_completions=<Bool> |
|
|||
107 | Whether to merge completion results into a single list |
|
|||
108 | If False, only the completion results from the first non-empty |
|
|||
109 | completer will be returned. |
|
|||
110 | As of version 8.6.0, setting the value to ``False`` is an alias for: |
|
|||
111 | ``IPCompleter.suppress_competing_matchers = True.``. |
|
|||
112 | Current: True |
|
|||
113 | IPCompleter.omit__names=<Enum> |
|
|||
114 | Instruct the completer to omit private method names |
|
|||
115 | Specifically, when completing on ``object.<tab>``. |
|
|||
116 | When 2 [default]: all names that start with '_' will be excluded. |
|
|||
117 | When 1: all 'magic' names (``__foo__``) will be excluded. |
|
|||
118 | When 0: nothing will be excluded. |
|
|||
119 | Choices: any of [0, 1, 2] |
|
|||
120 | Current: 2 |
|
|||
121 | IPCompleter.profile_completions=<Bool> |
|
|||
122 | If True, emit profiling data for completion subsystem using cProfile. |
|
|||
123 | Current: False |
|
|||
124 | IPCompleter.profiler_output_dir=<Unicode> |
|
|||
125 | Template for path at which to output profile data for completions. |
|
|||
126 | Current: '.completion_profiles' |
|
|||
127 | IPCompleter.suppress_competing_matchers=<Union> |
|
|||
128 | Whether to suppress completions from other *Matchers*. |
|
|||
129 | When set to ``None`` (default) the matchers will attempt to auto-detect |
|
|||
130 | whether suppression of other matchers is desirable. For example, at the |
|
|||
131 | beginning of a line followed by `%` we expect a magic completion to be the |
|
|||
132 | only applicable option, and after ``my_dict['`` we usually expect a |
|
|||
133 | completion with an existing dictionary key. |
|
|||
134 | If you want to disable this heuristic and see completions from all matchers, |
|
|||
135 | set ``IPCompleter.suppress_competing_matchers = False``. To disable the |
|
|||
136 | heuristic for specific matchers provide a dictionary mapping: |
|
|||
137 | ``IPCompleter.suppress_competing_matchers = {'IPCompleter.dict_key_matcher': |
|
|||
138 | False}``. |
|
|||
139 | Set ``IPCompleter.suppress_competing_matchers = True`` to limit completions |
|
|||
140 | to the set of matchers with the highest priority; this is equivalent to |
|
|||
141 | ``IPCompleter.merge_completions`` and can be beneficial for performance, but |
|
|||
142 | will sometimes omit relevant candidates from matchers further down the |
|
|||
143 | priority list. |
|
|||
144 | Current: None |
|
|||
145 | IPCompleter.use_jedi=<Bool> |
|
|||
146 | Experimental: Use Jedi to generate autocompletions. Default to True if jedi |
|
|||
147 | is installed. |
|
|||
148 | Current: True |
|
|||
149 |
|
77 | |||
150 | but the real use is in setting values:: |
|
78 | but the real use is in setting values:: | |
151 |
|
79 | |||
152 |
In [3]: %config |
|
80 | In [3]: %config LoggingMagics.quiet = True | |
153 |
|
81 | |||
154 | and these values are read from the user_ns if they are variables:: |
|
82 | and these values are read from the user_ns if they are variables:: | |
155 |
|
83 | |||
156 |
In [4]: feeling_ |
|
84 | In [4]: feeling_quiet=False | |
157 |
|
85 | |||
158 |
In [5]: %config |
|
86 | In [5]: %config LoggingMagics.quiet = feeling_quiet | |
159 |
|
87 | |||
160 | """ |
|
88 | """ | |
161 | from traitlets.config.loader import Config |
|
89 | from traitlets.config.loader import Config |
@@ -3,18 +3,18 b' from IPython.core.guarded_eval import (' | |||||
3 | EvaluationContext, |
|
3 | EvaluationContext, | |
4 | GuardRejection, |
|
4 | GuardRejection, | |
5 | guarded_eval, |
|
5 | guarded_eval, | |
6 | unbind_method, |
|
6 | _unbind_method, | |
7 | ) |
|
7 | ) | |
8 | from IPython.testing import decorators as dec |
|
8 | from IPython.testing import decorators as dec | |
9 | import pytest |
|
9 | import pytest | |
10 |
|
10 | |||
11 |
|
11 | |||
12 | def limited(**kwargs): |
|
12 | def limited(**kwargs): | |
13 |
return EvaluationContext(locals |
|
13 | return EvaluationContext(locals=kwargs, globals={}, evaluation="limited") | |
14 |
|
14 | |||
15 |
|
15 | |||
16 | def unsafe(**kwargs): |
|
16 | def unsafe(**kwargs): | |
17 |
return EvaluationContext(locals |
|
17 | return EvaluationContext(locals=kwargs, globals={}, evaluation="unsafe") | |
18 |
|
18 | |||
19 |
|
19 | |||
20 | @dec.skip_without("pandas") |
|
20 | @dec.skip_without("pandas") | |
@@ -206,7 +206,7 b' def test_access_builtins():' | |||||
206 |
|
206 | |||
207 | def test_subscript(): |
|
207 | def test_subscript(): | |
208 | context = EvaluationContext( |
|
208 | context = EvaluationContext( | |
209 |
locals |
|
209 | locals={}, globals={}, evaluation="limited", in_subscript=True | |
210 | ) |
|
210 | ) | |
211 | empty_slice = slice(None, None, None) |
|
211 | empty_slice = slice(None, None, None) | |
212 | assert guarded_eval("", context) == tuple() |
|
212 | assert guarded_eval("", context) == tuple() | |
@@ -221,8 +221,8 b' def test_unbind_method():' | |||||
221 | return "CUSTOM" |
|
221 | return "CUSTOM" | |
222 |
|
222 | |||
223 | x = X() |
|
223 | x = X() | |
224 | assert unbind_method(x.index) is X.index |
|
224 | assert _unbind_method(x.index) is X.index | |
225 | assert unbind_method([].index) is list.index |
|
225 | assert _unbind_method([].index) is list.index | |
226 |
|
226 | |||
227 |
|
227 | |||
228 | def test_assumption_instance_attr_do_not_matter(): |
|
228 | def test_assumption_instance_attr_do_not_matter(): |
General Comments 0
You need to be logged in to leave comments.
Login now