Show More
@@ -3,6 +3,7 b' from typing import (' | |||||
3 | Callable, |
|
3 | Callable, | |
4 | Dict, |
|
4 | Dict, | |
5 | Set, |
|
5 | Set, | |
|
6 | Sequence, | |||
6 | Tuple, |
|
7 | Tuple, | |
7 | NamedTuple, |
|
8 | NamedTuple, | |
8 | Type, |
|
9 | Type, | |
@@ -113,18 +114,30 b' class EvaluationPolicy:' | |||||
113 | return True |
|
114 | return True | |
114 |
|
115 | |||
115 |
|
116 | |||
|
117 | def _get_external(module_name: str, access_path: Sequence[str]): | |||
|
118 | """Get value from external module given a dotted access path. | |||
|
119 | ||||
|
120 | Raises: | |||
|
121 | * `KeyError` if module is removed not found, and | |||
|
122 | * `AttributeError` if acess path does not match an exported object | |||
|
123 | """ | |||
|
124 | member_type = sys.modules[module_name] | |||
|
125 | for attr in access_path: | |||
|
126 | member_type = getattr(member_type, attr) | |||
|
127 | return member_type | |||
|
128 | ||||
|
129 | ||||
116 | def _has_original_dunder_external( |
|
130 | def _has_original_dunder_external( | |
117 | value, |
|
131 | value, | |
118 | module_name, |
|
132 | module_name: str, | |
119 | access_path, |
|
133 | access_path: Sequence[str], | |
120 | method_name, |
|
134 | method_name: str, | |
121 | ): |
|
135 | ): | |
122 | try: |
|
|||
123 |
|
|
136 | if module_name not in sys.modules: | |
|
137 | # LBYLB as it is faster | |||
124 |
|
|
138 | return False | |
125 | member_type = sys.modules[module_name] |
|
139 | try: | |
126 | for attr in access_path: |
|
140 | member_type = _get_external(module_name, access_path) | |
127 | member_type = getattr(member_type, attr) |
|
|||
128 | value_type = type(value) |
|
141 | value_type = type(value) | |
129 | if type(value) == member_type: |
|
142 | if type(value) == member_type: | |
130 | return True |
|
143 | return True | |
@@ -199,12 +212,42 b' class SelectivePolicy(EvaluationPolicy):' | |||||
199 | method_name="__getattr__", |
|
212 | method_name="__getattr__", | |
200 | ) |
|
213 | ) | |
201 |
|
214 | |||
|
215 | accept = False | |||
|
216 | ||||
202 | # Many objects do not have `__getattr__`, this is fine |
|
217 | # Many objects do not have `__getattr__`, this is fine | |
203 | if has_original_attr is None and has_original_attribute: |
|
218 | if has_original_attr is None and has_original_attribute: | |
|
219 | accept = True | |||
|
220 | else: | |||
|
221 | # Accept objects without modifications to `__getattr__` and `__getattribute__` | |||
|
222 | accept = has_original_attr and has_original_attribute | |||
|
223 | ||||
|
224 | if accept: | |||
|
225 | # We still need to check for overriden properties. | |||
|
226 | ||||
|
227 | value_class = type(value) | |||
|
228 | if not hasattr(value_class, attr): | |||
204 | return True |
|
229 | return True | |
205 |
|
230 | |||
206 | # Accept objects without modifications to `__getattr__` and `__getattribute__` |
|
231 | class_attr_val = getattr(value_class, attr) | |
207 | return has_original_attr and has_original_attribute |
|
232 | is_property = isinstance(class_attr_val, property) | |
|
233 | ||||
|
234 | if not is_property: | |||
|
235 | return True | |||
|
236 | ||||
|
237 | # Properties in allowed types are ok | |||
|
238 | if type(value) in self.allowed_getattr: | |||
|
239 | return True | |||
|
240 | ||||
|
241 | # Properties in subclasses of allowed types may be ok if not changed | |||
|
242 | for module_name, *access_path in self.allowed_getattr_external: | |||
|
243 | try: | |||
|
244 | external_class = _get_external(module_name, access_path) | |||
|
245 | external_class_attr_val = getattr(external_class, attr) | |||
|
246 | except (KeyError, AttributeError): | |||
|
247 | return False # pragma: no cover | |||
|
248 | return class_attr_val == external_class_attr_val | |||
|
249 | ||||
|
250 | return False | |||
208 |
|
251 | |||
209 | def can_get_item(self, value, item): |
|
252 | def can_get_item(self, value, item): | |
210 | """Allow accessing `__getiitem__` of allow-listed instances unless it was not modified.""" |
|
253 | """Allow accessing `__getiitem__` of allow-listed instances unless it was not modified.""" |
@@ -1,3 +1,4 b'' | |||||
|
1 | from contextlib import contextmanager | |||
1 | from typing import NamedTuple |
|
2 | from typing import NamedTuple | |
2 | from functools import partial |
|
3 | from functools import partial | |
3 | from IPython.core.guarded_eval import ( |
|
4 | from IPython.core.guarded_eval import ( | |
@@ -25,6 +26,21 b' LIMITED_OR_HIGHER = [limited, unsafe, dangerous]' | |||||
25 | MINIMAL_OR_HIGHER = [minimal, *LIMITED_OR_HIGHER] |
|
26 | MINIMAL_OR_HIGHER = [minimal, *LIMITED_OR_HIGHER] | |
26 |
|
27 | |||
27 |
|
28 | |||
|
29 | @contextmanager | |||
|
30 | def module_not_installed(module: str): | |||
|
31 | import sys | |||
|
32 | ||||
|
33 | try: | |||
|
34 | to_restore = sys.modules[module] | |||
|
35 | del sys.modules[module] | |||
|
36 | except KeyError: | |||
|
37 | to_restore = None | |||
|
38 | try: | |||
|
39 | yield | |||
|
40 | finally: | |||
|
41 | sys.modules[module] = to_restore | |||
|
42 | ||||
|
43 | ||||
28 | @dec.skip_without("pandas") |
|
44 | @dec.skip_without("pandas") | |
29 | def test_pandas_series_iloc(): |
|
45 | def test_pandas_series_iloc(): | |
30 | import pandas as pd |
|
46 | import pandas as pd | |
@@ -34,6 +50,32 b' def test_pandas_series_iloc():' | |||||
34 | assert guarded_eval("data.iloc[0]", context) == 1 |
|
50 | assert guarded_eval("data.iloc[0]", context) == 1 | |
35 |
|
51 | |||
36 |
|
52 | |||
|
53 | def test_rejects_custom_properties(): | |||
|
54 | class BadProperty: | |||
|
55 | @property | |||
|
56 | def iloc(self): | |||
|
57 | return [None] | |||
|
58 | ||||
|
59 | series = BadProperty() | |||
|
60 | context = limited(data=series) | |||
|
61 | ||||
|
62 | with pytest.raises(GuardRejection): | |||
|
63 | guarded_eval("data.iloc[0]", context) | |||
|
64 | ||||
|
65 | ||||
|
66 | @dec.skip_without("pandas") | |||
|
67 | def test_accepts_non_overriden_properties(): | |||
|
68 | import pandas as pd | |||
|
69 | ||||
|
70 | class GoodProperty(pd.Series): | |||
|
71 | pass | |||
|
72 | ||||
|
73 | series = GoodProperty([1], index=["a"]) | |||
|
74 | context = limited(data=series) | |||
|
75 | ||||
|
76 | assert guarded_eval("data.iloc[0]", context) == 1 | |||
|
77 | ||||
|
78 | ||||
37 | @dec.skip_without("pandas") |
|
79 | @dec.skip_without("pandas") | |
38 | def test_pandas_series(): |
|
80 | def test_pandas_series(): | |
39 | import pandas as pd |
|
81 | import pandas as pd | |
@@ -472,9 +514,12 b' def test_assumption_instance_attr_do_not_matter():' | |||||
472 | def __getattr__(self, k): |
|
514 | def __getattr__(self, k): | |
473 | return "a" |
|
515 | return "a" | |
474 |
|
516 | |||
|
517 | def f(self): | |||
|
518 | return "b" | |||
|
519 | ||||
475 | t = T() |
|
520 | t = T() | |
476 |
t.__getitem__ = |
|
521 | t.__getitem__ = f | |
477 |
t.__getattr__ = |
|
522 | t.__getattr__ = f | |
478 | assert t[1] == "a" |
|
523 | assert t[1] == "a" | |
479 | assert t[1] == "a" |
|
524 | assert t[1] == "a" | |
480 |
|
525 |
General Comments 0
You need to be logged in to leave comments.
Login now