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