Show More
@@ -3,6 +3,7 b' from typing import (' | |||
|
3 | 3 | Callable, |
|
4 | 4 | Dict, |
|
5 | 5 | Set, |
|
6 | Sequence, | |
|
6 | 7 | Tuple, |
|
7 | 8 | NamedTuple, |
|
8 | 9 | Type, |
@@ -113,18 +114,30 b' class EvaluationPolicy:' | |||
|
113 | 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 | 130 | def _has_original_dunder_external( |
|
117 | 131 | value, |
|
118 | module_name, | |
|
119 | access_path, | |
|
120 | method_name, | |
|
132 | module_name: str, | |
|
133 | access_path: Sequence[str], | |
|
134 | method_name: str, | |
|
121 | 135 | ): |
|
122 | try: | |
|
123 | 136 |
|
|
137 | # LBYLB as it is faster | |
|
124 | 138 |
|
|
125 | member_type = sys.modules[module_name] | |
|
126 | for attr in access_path: | |
|
127 | member_type = getattr(member_type, attr) | |
|
139 | try: | |
|
140 | member_type = _get_external(module_name, access_path) | |
|
128 | 141 | value_type = type(value) |
|
129 | 142 | if type(value) == member_type: |
|
130 | 143 | return True |
@@ -199,12 +212,42 b' class SelectivePolicy(EvaluationPolicy):' | |||
|
199 | 212 | method_name="__getattr__", |
|
200 | 213 | ) |
|
201 | 214 | |
|
215 | accept = False | |
|
216 | ||
|
202 | 217 | # Many objects do not have `__getattr__`, this is fine |
|
203 | 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 | 229 | return True |
|
205 | 230 | |
|
206 | # Accept objects without modifications to `__getattr__` and `__getattribute__` | |
|
207 | return has_original_attr and has_original_attribute | |
|
231 | class_attr_val = getattr(value_class, attr) | |
|
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 | 252 | def can_get_item(self, value, item): |
|
210 | 253 | """Allow accessing `__getiitem__` of allow-listed instances unless it was not modified.""" |
@@ -1,3 +1,4 b'' | |||
|
1 | from contextlib import contextmanager | |
|
1 | 2 | from typing import NamedTuple |
|
2 | 3 | from functools import partial |
|
3 | 4 | from IPython.core.guarded_eval import ( |
@@ -25,6 +26,21 b' LIMITED_OR_HIGHER = [limited, unsafe, dangerous]' | |||
|
25 | 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 | 44 | @dec.skip_without("pandas") |
|
29 | 45 | def test_pandas_series_iloc(): |
|
30 | 46 | import pandas as pd |
@@ -34,6 +50,32 b' def test_pandas_series_iloc():' | |||
|
34 | 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 | 79 | @dec.skip_without("pandas") |
|
38 | 80 | def test_pandas_series(): |
|
39 | 81 | import pandas as pd |
@@ -472,9 +514,12 b' def test_assumption_instance_attr_do_not_matter():' | |||
|
472 | 514 | def __getattr__(self, k): |
|
473 | 515 | return "a" |
|
474 | 516 | |
|
517 | def f(self): | |
|
518 | return "b" | |
|
519 | ||
|
475 | 520 | t = T() |
|
476 |
t.__getitem__ = |
|
|
477 |
t.__getattr__ = |
|
|
521 | t.__getitem__ = f | |
|
522 | t.__getattr__ = f | |
|
478 | 523 | assert t[1] == "a" |
|
479 | 524 | assert t[1] == "a" |
|
480 | 525 |
General Comments 0
You need to be logged in to leave comments.
Login now