##// END OF EJS Templates
missing backticks (#14156)
Matthias Bussonnier -
r28392:22869800 merge
parent child Browse files
Show More
@@ -1,733 +1,733 b''
1 1 from typing import (
2 2 Any,
3 3 Callable,
4 4 Dict,
5 5 Set,
6 6 Sequence,
7 7 Tuple,
8 8 NamedTuple,
9 9 Type,
10 10 Literal,
11 11 Union,
12 12 TYPE_CHECKING,
13 13 )
14 14 import ast
15 15 import builtins
16 16 import collections
17 17 import operator
18 18 import sys
19 19 from functools import cached_property
20 20 from dataclasses import dataclass, field
21 21 from types import MethodDescriptorType, ModuleType
22 22
23 23 from IPython.utils.docs import GENERATING_DOCUMENTATION
24 24 from IPython.utils.decorators import undoc
25 25
26 26
27 27 if TYPE_CHECKING or GENERATING_DOCUMENTATION:
28 28 from typing_extensions import Protocol
29 29 else:
30 30 # do not require on runtime
31 31 Protocol = object # requires Python >=3.8
32 32
33 33
34 34 @undoc
35 35 class HasGetItem(Protocol):
36 36 def __getitem__(self, key) -> None:
37 37 ...
38 38
39 39
40 40 @undoc
41 41 class InstancesHaveGetItem(Protocol):
42 42 def __call__(self, *args, **kwargs) -> HasGetItem:
43 43 ...
44 44
45 45
46 46 @undoc
47 47 class HasGetAttr(Protocol):
48 48 def __getattr__(self, key) -> None:
49 49 ...
50 50
51 51
52 52 @undoc
53 53 class DoesNotHaveGetAttr(Protocol):
54 54 pass
55 55
56 56
57 57 # By default `__getattr__` is not explicitly implemented on most objects
58 58 MayHaveGetattr = Union[HasGetAttr, DoesNotHaveGetAttr]
59 59
60 60
61 61 def _unbind_method(func: Callable) -> Union[Callable, None]:
62 62 """Get unbound method for given bound method.
63 63
64 64 Returns None if cannot get unbound method, or method is already unbound.
65 65 """
66 66 owner = getattr(func, "__self__", None)
67 67 owner_class = type(owner)
68 68 name = getattr(func, "__name__", None)
69 69 instance_dict_overrides = getattr(owner, "__dict__", None)
70 70 if (
71 71 owner is not None
72 72 and name
73 73 and (
74 74 not instance_dict_overrides
75 75 or (instance_dict_overrides and name not in instance_dict_overrides)
76 76 )
77 77 ):
78 78 return getattr(owner_class, name)
79 79 return None
80 80
81 81
82 82 @undoc
83 83 @dataclass
84 84 class EvaluationPolicy:
85 85 """Definition of evaluation policy."""
86 86
87 87 allow_locals_access: bool = False
88 88 allow_globals_access: bool = False
89 89 allow_item_access: bool = False
90 90 allow_attr_access: bool = False
91 91 allow_builtins_access: bool = False
92 92 allow_all_operations: bool = False
93 93 allow_any_calls: bool = False
94 94 allowed_calls: Set[Callable] = field(default_factory=set)
95 95
96 96 def can_get_item(self, value, item):
97 97 return self.allow_item_access
98 98
99 99 def can_get_attr(self, value, attr):
100 100 return self.allow_attr_access
101 101
102 102 def can_operate(self, dunders: Tuple[str, ...], a, b=None):
103 103 if self.allow_all_operations:
104 104 return True
105 105
106 106 def can_call(self, func):
107 107 if self.allow_any_calls:
108 108 return True
109 109
110 110 if func in self.allowed_calls:
111 111 return True
112 112
113 113 owner_method = _unbind_method(func)
114 114
115 115 if owner_method and owner_method in self.allowed_calls:
116 116 return True
117 117
118 118
119 119 def _get_external(module_name: str, access_path: Sequence[str]):
120 120 """Get value from external module given a dotted access path.
121 121
122 122 Raises:
123 123 * `KeyError` if module is removed not found, and
124 124 * `AttributeError` if acess path does not match an exported object
125 125 """
126 126 member_type = sys.modules[module_name]
127 127 for attr in access_path:
128 128 member_type = getattr(member_type, attr)
129 129 return member_type
130 130
131 131
132 132 def _has_original_dunder_external(
133 133 value,
134 134 module_name: str,
135 135 access_path: Sequence[str],
136 136 method_name: str,
137 137 ):
138 138 if module_name not in sys.modules:
139 139 # LBYLB as it is faster
140 140 return False
141 141 try:
142 142 member_type = _get_external(module_name, access_path)
143 143 value_type = type(value)
144 144 if type(value) == member_type:
145 145 return True
146 146 if method_name == "__getattribute__":
147 147 # we have to short-circuit here due to an unresolved issue in
148 148 # `isinstance` implementation: https://bugs.python.org/issue32683
149 149 return False
150 150 if isinstance(value, member_type):
151 151 method = getattr(value_type, method_name, None)
152 152 member_method = getattr(member_type, method_name, None)
153 153 if member_method == method:
154 154 return True
155 155 except (AttributeError, KeyError):
156 156 return False
157 157
158 158
159 159 def _has_original_dunder(
160 160 value, allowed_types, allowed_methods, allowed_external, method_name
161 161 ):
162 162 # note: Python ignores `__getattr__`/`__getitem__` on instances,
163 163 # we only need to check at class level
164 164 value_type = type(value)
165 165
166 166 # strict type check passes β†’ no need to check method
167 167 if value_type in allowed_types:
168 168 return True
169 169
170 170 method = getattr(value_type, method_name, None)
171 171
172 172 if method is None:
173 173 return None
174 174
175 175 if method in allowed_methods:
176 176 return True
177 177
178 178 for module_name, *access_path in allowed_external:
179 179 if _has_original_dunder_external(value, module_name, access_path, method_name):
180 180 return True
181 181
182 182 return False
183 183
184 184
185 185 @undoc
186 186 @dataclass
187 187 class SelectivePolicy(EvaluationPolicy):
188 188 allowed_getitem: Set[InstancesHaveGetItem] = field(default_factory=set)
189 189 allowed_getitem_external: Set[Tuple[str, ...]] = field(default_factory=set)
190 190
191 191 allowed_getattr: Set[MayHaveGetattr] = field(default_factory=set)
192 192 allowed_getattr_external: Set[Tuple[str, ...]] = field(default_factory=set)
193 193
194 194 allowed_operations: Set = field(default_factory=set)
195 195 allowed_operations_external: Set[Tuple[str, ...]] = field(default_factory=set)
196 196
197 197 _operation_methods_cache: Dict[str, Set[Callable]] = field(
198 198 default_factory=dict, init=False
199 199 )
200 200
201 201 def can_get_attr(self, value, attr):
202 202 has_original_attribute = _has_original_dunder(
203 203 value,
204 204 allowed_types=self.allowed_getattr,
205 205 allowed_methods=self._getattribute_methods,
206 206 allowed_external=self.allowed_getattr_external,
207 207 method_name="__getattribute__",
208 208 )
209 209 has_original_attr = _has_original_dunder(
210 210 value,
211 211 allowed_types=self.allowed_getattr,
212 212 allowed_methods=self._getattr_methods,
213 213 allowed_external=self.allowed_getattr_external,
214 214 method_name="__getattr__",
215 215 )
216 216
217 217 accept = False
218 218
219 219 # Many objects do not have `__getattr__`, this is fine.
220 220 if has_original_attr is None and has_original_attribute:
221 221 accept = True
222 222 else:
223 223 # Accept objects without modifications to `__getattr__` and `__getattribute__`
224 224 accept = has_original_attr and has_original_attribute
225 225
226 226 if accept:
227 227 # We still need to check for overriden properties.
228 228
229 229 value_class = type(value)
230 230 if not hasattr(value_class, attr):
231 231 return True
232 232
233 233 class_attr_val = getattr(value_class, attr)
234 234 is_property = isinstance(class_attr_val, property)
235 235
236 236 if not is_property:
237 237 return True
238 238
239 239 # Properties in allowed types are ok (although we do not include any
240 240 # properties in our default allow list currently).
241 241 if type(value) in self.allowed_getattr:
242 242 return True # pragma: no cover
243 243
244 244 # Properties in subclasses of allowed types may be ok if not changed
245 245 for module_name, *access_path in self.allowed_getattr_external:
246 246 try:
247 247 external_class = _get_external(module_name, access_path)
248 248 external_class_attr_val = getattr(external_class, attr)
249 249 except (KeyError, AttributeError):
250 250 return False # pragma: no cover
251 251 return class_attr_val == external_class_attr_val
252 252
253 253 return False
254 254
255 255 def can_get_item(self, value, item):
256 256 """Allow accessing `__getiitem__` of allow-listed instances unless it was not modified."""
257 257 return _has_original_dunder(
258 258 value,
259 259 allowed_types=self.allowed_getitem,
260 260 allowed_methods=self._getitem_methods,
261 261 allowed_external=self.allowed_getitem_external,
262 262 method_name="__getitem__",
263 263 )
264 264
265 265 def can_operate(self, dunders: Tuple[str, ...], a, b=None):
266 266 objects = [a]
267 267 if b is not None:
268 268 objects.append(b)
269 269 return all(
270 270 [
271 271 _has_original_dunder(
272 272 obj,
273 273 allowed_types=self.allowed_operations,
274 274 allowed_methods=self._operator_dunder_methods(dunder),
275 275 allowed_external=self.allowed_operations_external,
276 276 method_name=dunder,
277 277 )
278 278 for dunder in dunders
279 279 for obj in objects
280 280 ]
281 281 )
282 282
283 283 def _operator_dunder_methods(self, dunder: str) -> Set[Callable]:
284 284 if dunder not in self._operation_methods_cache:
285 285 self._operation_methods_cache[dunder] = self._safe_get_methods(
286 286 self.allowed_operations, dunder
287 287 )
288 288 return self._operation_methods_cache[dunder]
289 289
290 290 @cached_property
291 291 def _getitem_methods(self) -> Set[Callable]:
292 292 return self._safe_get_methods(self.allowed_getitem, "__getitem__")
293 293
294 294 @cached_property
295 295 def _getattr_methods(self) -> Set[Callable]:
296 296 return self._safe_get_methods(self.allowed_getattr, "__getattr__")
297 297
298 298 @cached_property
299 299 def _getattribute_methods(self) -> Set[Callable]:
300 300 return self._safe_get_methods(self.allowed_getattr, "__getattribute__")
301 301
302 302 def _safe_get_methods(self, classes, name) -> Set[Callable]:
303 303 return {
304 304 method
305 305 for class_ in classes
306 306 for method in [getattr(class_, name, None)]
307 307 if method
308 308 }
309 309
310 310
311 311 class _DummyNamedTuple(NamedTuple):
312 312 """Used internally to retrieve methods of named tuple instance."""
313 313
314 314
315 315 class EvaluationContext(NamedTuple):
316 316 #: Local namespace
317 317 locals: dict
318 318 #: Global namespace
319 319 globals: dict
320 320 #: Evaluation policy identifier
321 321 evaluation: Literal[
322 322 "forbidden", "minimal", "limited", "unsafe", "dangerous"
323 323 ] = "forbidden"
324 324 #: Whether the evalution of code takes place inside of a subscript.
325 325 #: Useful for evaluating ``:-1, 'col'`` in ``df[:-1, 'col']``.
326 326 in_subscript: bool = False
327 327
328 328
329 329 class _IdentitySubscript:
330 330 """Returns the key itself when item is requested via subscript."""
331 331
332 332 def __getitem__(self, key):
333 333 return key
334 334
335 335
336 336 IDENTITY_SUBSCRIPT = _IdentitySubscript()
337 337 SUBSCRIPT_MARKER = "__SUBSCRIPT_SENTINEL__"
338 338
339 339
340 340 class GuardRejection(Exception):
341 341 """Exception raised when guard rejects evaluation attempt."""
342 342
343 343 pass
344 344
345 345
346 346 def guarded_eval(code: str, context: EvaluationContext):
347 347 """Evaluate provided code in the evaluation context.
348 348
349 349 If evaluation policy given by context is set to ``forbidden``
350 350 no evaluation will be performed; if it is set to ``dangerous``
351 351 standard :func:`eval` will be used; finally, for any other,
352 352 policy :func:`eval_node` will be called on parsed AST.
353 353 """
354 354 locals_ = context.locals
355 355
356 356 if context.evaluation == "forbidden":
357 357 raise GuardRejection("Forbidden mode")
358 358
359 359 # note: not using `ast.literal_eval` as it does not implement
360 360 # getitem at all, for example it fails on simple `[0][1]`
361 361
362 362 if context.in_subscript:
363 363 # syntatic sugar for ellipsis (:) is only available in susbcripts
364 364 # so we need to trick the ast parser into thinking that we have
365 365 # a subscript, but we need to be able to later recognise that we did
366 366 # it so we can ignore the actual __getitem__ operation
367 367 if not code:
368 368 return tuple()
369 369 locals_ = locals_.copy()
370 370 locals_[SUBSCRIPT_MARKER] = IDENTITY_SUBSCRIPT
371 371 code = SUBSCRIPT_MARKER + "[" + code + "]"
372 372 context = EvaluationContext(**{**context._asdict(), **{"locals": locals_}})
373 373
374 374 if context.evaluation == "dangerous":
375 375 return eval(code, context.globals, context.locals)
376 376
377 377 expression = ast.parse(code, mode="eval")
378 378
379 379 return eval_node(expression, context)
380 380
381 381
382 382 BINARY_OP_DUNDERS: Dict[Type[ast.operator], Tuple[str]] = {
383 383 ast.Add: ("__add__",),
384 384 ast.Sub: ("__sub__",),
385 385 ast.Mult: ("__mul__",),
386 386 ast.Div: ("__truediv__",),
387 387 ast.FloorDiv: ("__floordiv__",),
388 388 ast.Mod: ("__mod__",),
389 389 ast.Pow: ("__pow__",),
390 390 ast.LShift: ("__lshift__",),
391 391 ast.RShift: ("__rshift__",),
392 392 ast.BitOr: ("__or__",),
393 393 ast.BitXor: ("__xor__",),
394 394 ast.BitAnd: ("__and__",),
395 395 ast.MatMult: ("__matmul__",),
396 396 }
397 397
398 398 COMP_OP_DUNDERS: Dict[Type[ast.cmpop], Tuple[str, ...]] = {
399 399 ast.Eq: ("__eq__",),
400 400 ast.NotEq: ("__ne__", "__eq__"),
401 401 ast.Lt: ("__lt__", "__gt__"),
402 402 ast.LtE: ("__le__", "__ge__"),
403 403 ast.Gt: ("__gt__", "__lt__"),
404 404 ast.GtE: ("__ge__", "__le__"),
405 405 ast.In: ("__contains__",),
406 406 # Note: ast.Is, ast.IsNot, ast.NotIn are handled specially
407 407 }
408 408
409 409 UNARY_OP_DUNDERS: Dict[Type[ast.unaryop], Tuple[str, ...]] = {
410 410 ast.USub: ("__neg__",),
411 411 ast.UAdd: ("__pos__",),
412 412 # we have to check both __inv__ and __invert__!
413 413 ast.Invert: ("__invert__", "__inv__"),
414 414 ast.Not: ("__not__",),
415 415 }
416 416
417 417
418 418 def _find_dunder(node_op, dunders) -> Union[Tuple[str, ...], None]:
419 419 dunder = None
420 420 for op, candidate_dunder in dunders.items():
421 421 if isinstance(node_op, op):
422 422 dunder = candidate_dunder
423 423 return dunder
424 424
425 425
426 426 def eval_node(node: Union[ast.AST, None], context: EvaluationContext):
427 427 """Evaluate AST node in provided context.
428 428
429 429 Applies evaluation restrictions defined in the context. Currently does not support evaluation of functions with keyword arguments.
430 430
431 431 Does not evaluate actions that always have side effects:
432 432
433 433 - class definitions (``class sth: ...``)
434 434 - function definitions (``def sth: ...``)
435 435 - variable assignments (``x = 1``)
436 436 - augmented assignments (``x += 1``)
437 437 - deletions (``del x``)
438 438
439 439 Does not evaluate operations which do not return values:
440 440
441 441 - assertions (``assert x``)
442 442 - pass (``pass``)
443 443 - imports (``import x``)
444 444 - control flow:
445 445
446 446 - conditionals (``if x:``) except for ternary IfExp (``a if x else b``)
447 - loops (``for`` and `while``)
447 - loops (``for`` and ``while``)
448 448 - exception handling
449 449
450 450 The purpose of this function is to guard against unwanted side-effects;
451 451 it does not give guarantees on protection from malicious code execution.
452 452 """
453 453 policy = EVALUATION_POLICIES[context.evaluation]
454 454 if node is None:
455 455 return None
456 456 if isinstance(node, ast.Expression):
457 457 return eval_node(node.body, context)
458 458 if isinstance(node, ast.BinOp):
459 459 left = eval_node(node.left, context)
460 460 right = eval_node(node.right, context)
461 461 dunders = _find_dunder(node.op, BINARY_OP_DUNDERS)
462 462 if dunders:
463 463 if policy.can_operate(dunders, left, right):
464 464 return getattr(left, dunders[0])(right)
465 465 else:
466 466 raise GuardRejection(
467 467 f"Operation (`{dunders}`) for",
468 468 type(left),
469 469 f"not allowed in {context.evaluation} mode",
470 470 )
471 471 if isinstance(node, ast.Compare):
472 472 left = eval_node(node.left, context)
473 473 all_true = True
474 474 negate = False
475 475 for op, right in zip(node.ops, node.comparators):
476 476 right = eval_node(right, context)
477 477 dunder = None
478 478 dunders = _find_dunder(op, COMP_OP_DUNDERS)
479 479 if not dunders:
480 480 if isinstance(op, ast.NotIn):
481 481 dunders = COMP_OP_DUNDERS[ast.In]
482 482 negate = True
483 483 if isinstance(op, ast.Is):
484 484 dunder = "is_"
485 485 if isinstance(op, ast.IsNot):
486 486 dunder = "is_"
487 487 negate = True
488 488 if not dunder and dunders:
489 489 dunder = dunders[0]
490 490 if dunder:
491 491 a, b = (right, left) if dunder == "__contains__" else (left, right)
492 492 if dunder == "is_" or dunders and policy.can_operate(dunders, a, b):
493 493 result = getattr(operator, dunder)(a, b)
494 494 if negate:
495 495 result = not result
496 496 if not result:
497 497 all_true = False
498 498 left = right
499 499 else:
500 500 raise GuardRejection(
501 501 f"Comparison (`{dunder}`) for",
502 502 type(left),
503 503 f"not allowed in {context.evaluation} mode",
504 504 )
505 505 else:
506 506 raise ValueError(
507 507 f"Comparison `{dunder}` not supported"
508 508 ) # pragma: no cover
509 509 return all_true
510 510 if isinstance(node, ast.Constant):
511 511 return node.value
512 512 if isinstance(node, ast.Tuple):
513 513 return tuple(eval_node(e, context) for e in node.elts)
514 514 if isinstance(node, ast.List):
515 515 return [eval_node(e, context) for e in node.elts]
516 516 if isinstance(node, ast.Set):
517 517 return {eval_node(e, context) for e in node.elts}
518 518 if isinstance(node, ast.Dict):
519 519 return dict(
520 520 zip(
521 521 [eval_node(k, context) for k in node.keys],
522 522 [eval_node(v, context) for v in node.values],
523 523 )
524 524 )
525 525 if isinstance(node, ast.Slice):
526 526 return slice(
527 527 eval_node(node.lower, context),
528 528 eval_node(node.upper, context),
529 529 eval_node(node.step, context),
530 530 )
531 531 if isinstance(node, ast.UnaryOp):
532 532 value = eval_node(node.operand, context)
533 533 dunders = _find_dunder(node.op, UNARY_OP_DUNDERS)
534 534 if dunders:
535 535 if policy.can_operate(dunders, value):
536 536 return getattr(value, dunders[0])()
537 537 else:
538 538 raise GuardRejection(
539 539 f"Operation (`{dunders}`) for",
540 540 type(value),
541 541 f"not allowed in {context.evaluation} mode",
542 542 )
543 543 if isinstance(node, ast.Subscript):
544 544 value = eval_node(node.value, context)
545 545 slice_ = eval_node(node.slice, context)
546 546 if policy.can_get_item(value, slice_):
547 547 return value[slice_]
548 548 raise GuardRejection(
549 549 "Subscript access (`__getitem__`) for",
550 550 type(value), # not joined to avoid calling `repr`
551 551 f" not allowed in {context.evaluation} mode",
552 552 )
553 553 if isinstance(node, ast.Name):
554 554 if policy.allow_locals_access and node.id in context.locals:
555 555 return context.locals[node.id]
556 556 if policy.allow_globals_access and node.id in context.globals:
557 557 return context.globals[node.id]
558 558 if policy.allow_builtins_access and hasattr(builtins, node.id):
559 559 # note: do not use __builtins__, it is implementation detail of cPython
560 560 return getattr(builtins, node.id)
561 561 if not policy.allow_globals_access and not policy.allow_locals_access:
562 562 raise GuardRejection(
563 563 f"Namespace access not allowed in {context.evaluation} mode"
564 564 )
565 565 else:
566 566 raise NameError(f"{node.id} not found in locals, globals, nor builtins")
567 567 if isinstance(node, ast.Attribute):
568 568 value = eval_node(node.value, context)
569 569 if policy.can_get_attr(value, node.attr):
570 570 return getattr(value, node.attr)
571 571 raise GuardRejection(
572 572 "Attribute access (`__getattr__`) for",
573 573 type(value), # not joined to avoid calling `repr`
574 574 f"not allowed in {context.evaluation} mode",
575 575 )
576 576 if isinstance(node, ast.IfExp):
577 577 test = eval_node(node.test, context)
578 578 if test:
579 579 return eval_node(node.body, context)
580 580 else:
581 581 return eval_node(node.orelse, context)
582 582 if isinstance(node, ast.Call):
583 583 func = eval_node(node.func, context)
584 584 if policy.can_call(func) and not node.keywords:
585 585 args = [eval_node(arg, context) for arg in node.args]
586 586 return func(*args)
587 587 raise GuardRejection(
588 588 "Call for",
589 589 func, # not joined to avoid calling `repr`
590 590 f"not allowed in {context.evaluation} mode",
591 591 )
592 592 raise ValueError("Unhandled node", ast.dump(node))
593 593
594 594
595 595 SUPPORTED_EXTERNAL_GETITEM = {
596 596 ("pandas", "core", "indexing", "_iLocIndexer"),
597 597 ("pandas", "core", "indexing", "_LocIndexer"),
598 598 ("pandas", "DataFrame"),
599 599 ("pandas", "Series"),
600 600 ("numpy", "ndarray"),
601 601 ("numpy", "void"),
602 602 }
603 603
604 604
605 605 BUILTIN_GETITEM: Set[InstancesHaveGetItem] = {
606 606 dict,
607 607 str, # type: ignore[arg-type]
608 608 bytes, # type: ignore[arg-type]
609 609 list,
610 610 tuple,
611 611 collections.defaultdict,
612 612 collections.deque,
613 613 collections.OrderedDict,
614 614 collections.ChainMap,
615 615 collections.UserDict,
616 616 collections.UserList,
617 617 collections.UserString, # type: ignore[arg-type]
618 618 _DummyNamedTuple,
619 619 _IdentitySubscript,
620 620 }
621 621
622 622
623 623 def _list_methods(cls, source=None):
624 624 """For use on immutable objects or with methods returning a copy"""
625 625 return [getattr(cls, k) for k in (source if source else dir(cls))]
626 626
627 627
628 628 dict_non_mutating_methods = ("copy", "keys", "values", "items")
629 629 list_non_mutating_methods = ("copy", "index", "count")
630 630 set_non_mutating_methods = set(dir(set)) & set(dir(frozenset))
631 631
632 632
633 633 dict_keys: Type[collections.abc.KeysView] = type({}.keys())
634 634
635 635 NUMERICS = {int, float, complex}
636 636
637 637 ALLOWED_CALLS = {
638 638 bytes,
639 639 *_list_methods(bytes),
640 640 dict,
641 641 *_list_methods(dict, dict_non_mutating_methods),
642 642 dict_keys.isdisjoint,
643 643 list,
644 644 *_list_methods(list, list_non_mutating_methods),
645 645 set,
646 646 *_list_methods(set, set_non_mutating_methods),
647 647 frozenset,
648 648 *_list_methods(frozenset),
649 649 range,
650 650 str,
651 651 *_list_methods(str),
652 652 tuple,
653 653 *_list_methods(tuple),
654 654 *NUMERICS,
655 655 *[method for numeric_cls in NUMERICS for method in _list_methods(numeric_cls)],
656 656 collections.deque,
657 657 *_list_methods(collections.deque, list_non_mutating_methods),
658 658 collections.defaultdict,
659 659 *_list_methods(collections.defaultdict, dict_non_mutating_methods),
660 660 collections.OrderedDict,
661 661 *_list_methods(collections.OrderedDict, dict_non_mutating_methods),
662 662 collections.UserDict,
663 663 *_list_methods(collections.UserDict, dict_non_mutating_methods),
664 664 collections.UserList,
665 665 *_list_methods(collections.UserList, list_non_mutating_methods),
666 666 collections.UserString,
667 667 *_list_methods(collections.UserString, dir(str)),
668 668 collections.Counter,
669 669 *_list_methods(collections.Counter, dict_non_mutating_methods),
670 670 collections.Counter.elements,
671 671 collections.Counter.most_common,
672 672 }
673 673
674 674 BUILTIN_GETATTR: Set[MayHaveGetattr] = {
675 675 *BUILTIN_GETITEM,
676 676 set,
677 677 frozenset,
678 678 object,
679 679 type, # `type` handles a lot of generic cases, e.g. numbers as in `int.real`.
680 680 *NUMERICS,
681 681 dict_keys,
682 682 MethodDescriptorType,
683 683 ModuleType,
684 684 }
685 685
686 686
687 687 BUILTIN_OPERATIONS = {*BUILTIN_GETATTR}
688 688
689 689 EVALUATION_POLICIES = {
690 690 "minimal": EvaluationPolicy(
691 691 allow_builtins_access=True,
692 692 allow_locals_access=False,
693 693 allow_globals_access=False,
694 694 allow_item_access=False,
695 695 allow_attr_access=False,
696 696 allowed_calls=set(),
697 697 allow_any_calls=False,
698 698 allow_all_operations=False,
699 699 ),
700 700 "limited": SelectivePolicy(
701 701 allowed_getitem=BUILTIN_GETITEM,
702 702 allowed_getitem_external=SUPPORTED_EXTERNAL_GETITEM,
703 703 allowed_getattr=BUILTIN_GETATTR,
704 704 allowed_getattr_external={
705 705 # pandas Series/Frame implements custom `__getattr__`
706 706 ("pandas", "DataFrame"),
707 707 ("pandas", "Series"),
708 708 },
709 709 allowed_operations=BUILTIN_OPERATIONS,
710 710 allow_builtins_access=True,
711 711 allow_locals_access=True,
712 712 allow_globals_access=True,
713 713 allowed_calls=ALLOWED_CALLS,
714 714 ),
715 715 "unsafe": EvaluationPolicy(
716 716 allow_builtins_access=True,
717 717 allow_locals_access=True,
718 718 allow_globals_access=True,
719 719 allow_attr_access=True,
720 720 allow_item_access=True,
721 721 allow_any_calls=True,
722 722 allow_all_operations=True,
723 723 ),
724 724 }
725 725
726 726
727 727 __all__ = [
728 728 "guarded_eval",
729 729 "eval_node",
730 730 "GuardRejection",
731 731 "EvaluationContext",
732 732 "_unbind_method",
733 733 ]
General Comments 0
You need to be logged in to leave comments. Login now