Show More
@@ -916,15 +916,15 b' class Completer(Configurable):' | |||||
916 | .. deprecated:: 8.8 |
|
916 | .. deprecated:: 8.8 | |
917 | Use :any:`evaluation` and :any:`auto_close_dict_keys` instead. |
|
917 | Use :any:`evaluation` and :any:`auto_close_dict_keys` instead. | |
918 |
|
918 | |||
919 |
When |
|
919 | When enabled in IPython 8.8+ activates following settings for compatibility: | |
920 | - ``evaluation = 'unsafe'`` |
|
920 | - ``evaluation = 'unsafe'`` | |
921 | - ``auto_close_dict_keys = True`` |
|
921 | - ``auto_close_dict_keys = True`` | |
922 | """, |
|
922 | """, | |
923 | ).tag(config=True) |
|
923 | ).tag(config=True) | |
924 |
|
924 | |||
925 | evaluation = Enum( |
|
925 | evaluation = Enum( | |
926 |
("forbidden", "minimal", "limit |
|
926 | ("forbidden", "minimal", "limited", "unsafe", "dangerous"), | |
927 |
default_value="limit |
|
927 | default_value="limited", | |
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, | |
@@ -934,7 +934,7 b' class Completer(Configurable):' | |||||
934 | Allowed values are: |
|
934 | Allowed values are: | |
935 | - `forbidden`: no evaluation at all |
|
935 | - `forbidden`: no evaluation at all | |
936 | - `minimal`: evaluation of literals and access to built-in namespaces; no item/attribute evaluation nor access to locals/globals |
|
936 | - `minimal`: evaluation of literals and access to built-in namespaces; no item/attribute evaluation nor access to locals/globals | |
937 |
- `limit |
|
937 | - `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``) | |
938 | - `unsafe`: evaluation of all methods and function calls but not of syntax with side-effects like `del x`, |
|
938 | - `unsafe`: evaluation of all methods and function calls but not of syntax with side-effects like `del x`, | |
939 | - `dangerous`: completely arbitrary evaluation |
|
939 | - `dangerous`: completely arbitrary evaluation | |
940 | """, |
|
940 | """, | |
@@ -1651,7 +1651,7 b' class IPCompleter(Completer):' | |||||
1651 | self.auto_close_dict_keys = True |
|
1651 | self.auto_close_dict_keys = True | |
1652 | self.splitter.delims = GREEDY_DELIMS |
|
1652 | self.splitter.delims = GREEDY_DELIMS | |
1653 | else: |
|
1653 | else: | |
1654 |
self.evaluation = "limit |
|
1654 | self.evaluation = "limited" | |
1655 | self.auto_close_dict_keys = False |
|
1655 | self.auto_close_dict_keys = False | |
1656 | self.splitter.delims = DELIMS |
|
1656 | self.splitter.delims = DELIMS | |
1657 |
|
1657 |
@@ -207,7 +207,7 b' class EvaluationContext(NamedTuple):' | |||||
207 | locals_: dict |
|
207 | locals_: dict | |
208 | globals_: dict |
|
208 | globals_: dict | |
209 | evaluation: Literal[ |
|
209 | evaluation: Literal[ | |
210 |
"forbidden", "minimal", "limit |
|
210 | "forbidden", "minimal", "limited", "unsafe", "dangerous" | |
211 | ] = "forbidden" |
|
211 | ] = "forbidden" | |
212 | in_subscript: bool = False |
|
212 | in_subscript: bool = False | |
213 |
|
213 | |||
@@ -260,13 +260,13 b' def eval_node(node: Union[ast.AST, None], context: EvaluationContext):' | |||||
260 |
|
260 | |||
261 | Applies evaluation restrictions defined in the context. |
|
261 | Applies evaluation restrictions defined in the context. | |
262 |
|
262 | |||
263 | Currently does not support evaluation of functions with arguments. |
|
263 | Currently does not support evaluation of functions with keyword arguments. | |
264 |
|
264 | |||
265 | Does not evaluate actions which always have side effects: |
|
265 | Does not evaluate actions which always have side effects: | |
266 | - class definitions (``class sth: ...``) |
|
266 | - class definitions (``class sth: ...``) | |
267 | - function definitions (``def sth: ...``) |
|
267 | - function definitions (``def sth: ...``) | |
268 | - variable assignments (``x = 1``) |
|
268 | - variable assignments (``x = 1``) | |
269 |
- aug |
|
269 | - augmented assignments (``x += 1``) | |
270 | - deletions (``del x``) |
|
270 | - deletions (``del x``) | |
271 |
|
271 | |||
272 | Does not evaluate operations which do not return values: |
|
272 | Does not evaluate operations which do not return values: | |
@@ -274,7 +274,7 b' def eval_node(node: Union[ast.AST, None], context: EvaluationContext):' | |||||
274 | - pass (``pass``) |
|
274 | - pass (``pass``) | |
275 | - imports (``import x``) |
|
275 | - imports (``import x``) | |
276 | - control flow |
|
276 | - control flow | |
277 |
- conditionals (``if x:``) except for ter |
|
277 | - conditionals (``if x:``) except for ternary IfExp (``a if x else b``) | |
278 | - loops (``for`` and `while``) |
|
278 | - loops (``for`` and `while``) | |
279 | - exception handling |
|
279 | - exception handling | |
280 |
|
280 | |||
@@ -393,7 +393,6 b' def eval_node(node: Union[ast.AST, None], context: EvaluationContext):' | |||||
393 | return eval_node(node.orelse, context) |
|
393 | return eval_node(node.orelse, context) | |
394 | if isinstance(node, ast.Call): |
|
394 | if isinstance(node, ast.Call): | |
395 | func = eval_node(node.func, context) |
|
395 | func = eval_node(node.func, context) | |
396 | print(node.keywords) |
|
|||
397 | if policy.can_call(func) and not node.keywords: |
|
396 | if policy.can_call(func) and not node.keywords: | |
398 | args = [eval_node(arg, context) for arg in node.args] |
|
397 | args = [eval_node(arg, context) for arg in node.args] | |
399 | return func(*args) |
|
398 | return func(*args) | |
@@ -490,7 +489,7 b' EVALUATION_POLICIES = {' | |||||
490 | allowed_calls=set(), |
|
489 | allowed_calls=set(), | |
491 | allow_any_calls=False, |
|
490 | allow_any_calls=False, | |
492 | ), |
|
491 | ), | |
493 |
"limit |
|
492 | "limited": SelectivePolicy( | |
494 | # TODO: |
|
493 | # TODO: | |
495 | # - should reject binary and unary operations if custom methods would be dispatched |
|
494 | # - should reject binary and unary operations if custom methods would be dispatched | |
496 | allowed_getitem=BUILTIN_GETITEM, |
|
495 | allowed_getitem=BUILTIN_GETITEM, |
@@ -1354,7 +1354,7 b' class TestCompleter(unittest.TestCase):' | |||||
1354 | with greedy_completion(): |
|
1354 | with greedy_completion(): | |
1355 | completes_on_nested() |
|
1355 | completes_on_nested() | |
1356 |
|
1356 | |||
1357 |
with evaluation_level("limit |
|
1357 | with evaluation_level("limited"): | |
1358 | completes_on_nested() |
|
1358 | completes_on_nested() | |
1359 |
|
1359 | |||
1360 | with evaluation_level("minimal"): |
|
1360 | with evaluation_level("minimal"): |
@@ -9,8 +9,8 b' from IPython.testing import decorators as dec' | |||||
9 | import pytest |
|
9 | import pytest | |
10 |
|
10 | |||
11 |
|
11 | |||
12 |
def limit |
|
12 | def limited(**kwargs): | |
13 |
return EvaluationContext(locals_=kwargs, globals_={}, evaluation="limit |
|
13 | return EvaluationContext(locals_=kwargs, globals_={}, evaluation="limited") | |
14 |
|
14 | |||
15 |
|
15 | |||
16 | def unsafe(**kwargs): |
|
16 | def unsafe(**kwargs): | |
@@ -22,7 +22,7 b' def test_pandas_series_iloc():' | |||||
22 | import pandas as pd |
|
22 | import pandas as pd | |
23 |
|
23 | |||
24 | series = pd.Series([1], index=["a"]) |
|
24 | series = pd.Series([1], index=["a"]) | |
25 |
context = limit |
|
25 | context = limited(data=series) | |
26 | assert guarded_eval("data.iloc[0]", context) == 1 |
|
26 | assert guarded_eval("data.iloc[0]", context) == 1 | |
27 |
|
27 | |||
28 |
|
28 | |||
@@ -30,7 +30,7 b' def test_pandas_series_iloc():' | |||||
30 | def test_pandas_series(): |
|
30 | def test_pandas_series(): | |
31 | import pandas as pd |
|
31 | import pandas as pd | |
32 |
|
32 | |||
33 |
context = limit |
|
33 | context = limited(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) | |
@@ -49,7 +49,7 b' def test_pandas_bad_series():' | |||||
49 | return "CUSTOM_ATTR" |
|
49 | return "CUSTOM_ATTR" | |
50 |
|
50 | |||
51 | bad_series = BadItemSeries([1], index=["a"]) |
|
51 | bad_series = BadItemSeries([1], index=["a"]) | |
52 |
context = limit |
|
52 | context = limited(data=bad_series) | |
53 |
|
53 | |||
54 | with pytest.raises(GuardRejection): |
|
54 | with pytest.raises(GuardRejection): | |
55 | guarded_eval('data["a"]', context) |
|
55 | guarded_eval('data["a"]', context) | |
@@ -65,7 +65,7 b' def test_pandas_bad_series():' | |||||
65 | assert guarded_eval('data["a"]', context) == "CUSTOM_ITEM" |
|
65 | assert guarded_eval('data["a"]', context) == "CUSTOM_ITEM" | |
66 |
|
66 | |||
67 | bad_attr_series = BadAttrSeries([1], index=["a"]) |
|
67 | bad_attr_series = BadAttrSeries([1], index=["a"]) | |
68 |
context = limit |
|
68 | context = limited(data=bad_attr_series) | |
69 | assert guarded_eval('data["a"]', context) == 1 |
|
69 | assert guarded_eval('data["a"]', context) == 1 | |
70 | with pytest.raises(GuardRejection): |
|
70 | with pytest.raises(GuardRejection): | |
71 | guarded_eval("data.a", context) |
|
71 | guarded_eval("data.a", context) | |
@@ -77,7 +77,7 b' def test_pandas_dataframe_loc():' | |||||
77 | from pandas.testing import assert_series_equal |
|
77 | from pandas.testing import assert_series_equal | |
78 |
|
78 | |||
79 | data = pd.DataFrame([{"a": 1}]) |
|
79 | data = pd.DataFrame([{"a": 1}]) | |
80 |
context = limit |
|
80 | context = limited(data=data) | |
81 | assert_series_equal(guarded_eval('data.loc[:, "a"]', context), data["a"]) |
|
81 | assert_series_equal(guarded_eval('data.loc[:, "a"]', context), data["a"]) | |
82 |
|
82 | |||
83 |
|
83 | |||
@@ -95,16 +95,16 b' def test_named_tuple():' | |||||
95 | good = GoodNamedTuple(a="x") |
|
95 | good = GoodNamedTuple(a="x") | |
96 | bad = BadNamedTuple(a="x") |
|
96 | bad = BadNamedTuple(a="x") | |
97 |
|
97 | |||
98 |
context = limit |
|
98 | context = limited(data=good) | |
99 | assert guarded_eval("data[0]", context) == "x" |
|
99 | assert guarded_eval("data[0]", context) == "x" | |
100 |
|
100 | |||
101 |
context = limit |
|
101 | context = limited(data=bad) | |
102 | with pytest.raises(GuardRejection): |
|
102 | with pytest.raises(GuardRejection): | |
103 | guarded_eval("data[0]", context) |
|
103 | guarded_eval("data[0]", context) | |
104 |
|
104 | |||
105 |
|
105 | |||
106 | def test_dict(): |
|
106 | def test_dict(): | |
107 |
context = limit |
|
107 | context = limited(data={"a": 1, "b": {"x": 2}, ("x", "y"): 3}) | |
108 | assert guarded_eval('data["a"]', context) == 1 |
|
108 | assert guarded_eval('data["a"]', context) == 1 | |
109 | assert guarded_eval('data["b"]', context) == {"x": 2} |
|
109 | assert guarded_eval('data["b"]', context) == {"x": 2} | |
110 | assert guarded_eval('data["b"]["x"]', context) == 2 |
|
110 | assert guarded_eval('data["b"]["x"]', context) == 2 | |
@@ -114,43 +114,43 b' def test_dict():' | |||||
114 |
|
114 | |||
115 |
|
115 | |||
116 | def test_set(): |
|
116 | def test_set(): | |
117 |
context = limit |
|
117 | context = limited(data={"a", "b"}) | |
118 | assert guarded_eval("data.difference", context) |
|
118 | assert guarded_eval("data.difference", context) | |
119 |
|
119 | |||
120 |
|
120 | |||
121 | def test_list(): |
|
121 | def test_list(): | |
122 |
context = limit |
|
122 | context = limited(data=[1, 2, 3]) | |
123 | assert guarded_eval("data[1]", context) == 2 |
|
123 | assert guarded_eval("data[1]", context) == 2 | |
124 | assert guarded_eval("data.copy", context) |
|
124 | assert guarded_eval("data.copy", context) | |
125 |
|
125 | |||
126 |
|
126 | |||
127 | def test_dict_literal(): |
|
127 | def test_dict_literal(): | |
128 |
context = limit |
|
128 | context = limited() | |
129 | assert guarded_eval("{}", context) == {} |
|
129 | assert guarded_eval("{}", context) == {} | |
130 | assert guarded_eval('{"a": 1}', context) == {"a": 1} |
|
130 | assert guarded_eval('{"a": 1}', context) == {"a": 1} | |
131 |
|
131 | |||
132 |
|
132 | |||
133 | def test_list_literal(): |
|
133 | def test_list_literal(): | |
134 |
context = limit |
|
134 | context = limited() | |
135 | assert guarded_eval("[]", context) == [] |
|
135 | assert guarded_eval("[]", context) == [] | |
136 | assert guarded_eval('[1, "a"]', context) == [1, "a"] |
|
136 | assert guarded_eval('[1, "a"]', context) == [1, "a"] | |
137 |
|
137 | |||
138 |
|
138 | |||
139 | def test_set_literal(): |
|
139 | def test_set_literal(): | |
140 |
context = limit |
|
140 | context = limited() | |
141 | assert guarded_eval("set()", context) == set() |
|
141 | assert guarded_eval("set()", context) == set() | |
142 | assert guarded_eval('{"a"}', context) == {"a"} |
|
142 | assert guarded_eval('{"a"}', context) == {"a"} | |
143 |
|
143 | |||
144 |
|
144 | |||
145 | def test_if_expression(): |
|
145 | def test_if_expression(): | |
146 |
context = limit |
|
146 | context = limited() | |
147 | assert guarded_eval("2 if True else 3", context) == 2 |
|
147 | assert guarded_eval("2 if True else 3", context) == 2 | |
148 | assert guarded_eval("4 if False else 5", context) == 5 |
|
148 | assert guarded_eval("4 if False else 5", context) == 5 | |
149 |
|
149 | |||
150 |
|
150 | |||
151 | def test_object(): |
|
151 | def test_object(): | |
152 | obj = object() |
|
152 | obj = object() | |
153 |
context = limit |
|
153 | context = limited(obj=obj) | |
154 | assert guarded_eval("obj.__dir__", context) == obj.__dir__ |
|
154 | assert guarded_eval("obj.__dir__", context) == obj.__dir__ | |
155 |
|
155 | |||
156 |
|
156 | |||
@@ -163,11 +163,11 b' def test_object():' | |||||
163 | ], |
|
163 | ], | |
164 | ) |
|
164 | ) | |
165 | def test_number_attributes(code, expected): |
|
165 | def test_number_attributes(code, expected): | |
166 |
assert guarded_eval(code, limit |
|
166 | assert guarded_eval(code, limited()) == expected | |
167 |
|
167 | |||
168 |
|
168 | |||
169 | def test_method_descriptor(): |
|
169 | def test_method_descriptor(): | |
170 |
context = limit |
|
170 | context = limited() | |
171 | assert guarded_eval("list.copy.__name__", context) == "copy" |
|
171 | assert guarded_eval("list.copy.__name__", context) == "copy" | |
172 |
|
172 | |||
173 |
|
173 | |||
@@ -179,7 +179,7 b' def test_method_descriptor():' | |||||
179 | ], |
|
179 | ], | |
180 | ) |
|
180 | ) | |
181 | def test_calls(data, good, bad, expected): |
|
181 | def test_calls(data, good, bad, expected): | |
182 |
context = limit |
|
182 | context = limited(data=data) | |
183 | assert guarded_eval(good, context) == expected |
|
183 | assert guarded_eval(good, context) == expected | |
184 |
|
184 | |||
185 | with pytest.raises(GuardRejection): |
|
185 | with pytest.raises(GuardRejection): | |
@@ -195,13 +195,13 b' def test_calls(data, good, bad, expected):' | |||||
195 | ], |
|
195 | ], | |
196 | ) |
|
196 | ) | |
197 | def test_literals(code, expected): |
|
197 | def test_literals(code, expected): | |
198 |
context = limit |
|
198 | context = limited() | |
199 | assert guarded_eval(code, context) == expected |
|
199 | assert guarded_eval(code, context) == expected | |
200 |
|
200 | |||
201 |
|
201 | |||
202 | def test_subscript(): |
|
202 | def test_subscript(): | |
203 | context = EvaluationContext( |
|
203 | context = EvaluationContext( | |
204 |
locals_={}, globals_={}, evaluation="limit |
|
204 | locals_={}, globals_={}, evaluation="limited", in_subscript=True | |
205 | ) |
|
205 | ) | |
206 | empty_slice = slice(None, None, None) |
|
206 | empty_slice = slice(None, None, None) | |
207 | assert guarded_eval("", context) == tuple() |
|
207 | assert guarded_eval("", context) == tuple() |
General Comments 0
You need to be logged in to leave comments.
Login now