Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/joblib/_utils.py: 39%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# Adapted from https://stackoverflow.com/a/9558001/2536294
3import ast
4import functools
5import operator as op
6from dataclasses import dataclass
8from ._multiprocessing_helpers import mp
10if mp is not None:
11 from .externals.loky.process_executor import _ExceptionWithTraceback
14# supported operators
15operators = {
16 ast.Add: op.add,
17 ast.Sub: op.sub,
18 ast.Mult: op.mul,
19 ast.Div: op.truediv,
20 ast.FloorDiv: op.floordiv,
21 ast.Mod: op.mod,
22 ast.Pow: op.pow,
23 ast.USub: op.neg,
24}
26# Largest magnitude any sub-expression is allowed to reach.
27MAX_VALUE = 10**6
30def _pow_is_too_large(base, exponent):
31 """Return True if ``base ** exponent`` cannot fit under `MAX_VALUE`.
33 Only integers are considered. A float power overflows cheaply, whereas a
34 large integer power is built out in full before `limit` ever sees the
35 result: ``999999**999999`` costs seconds of CPU and allocates a number with
36 millions of digits.
37 """
38 if not (isinstance(base, int) and isinstance(exponent, int)):
39 return False
40 # For any |base| >= 2, base ** exponent >= 2 ** exponent, so an exponent
41 # above the bit length of the limit cannot produce a value that fits.
42 return abs(base) >= 2 and exponent > MAX_VALUE.bit_length()
45def eval_expr(expr):
46 """Somewhat safely evaluate an arithmetic expression.
48 >>> eval_expr('2*6')
49 12
50 >>> eval_expr('2**6')
51 64
52 >>> eval_expr('1 + 2*3**(4) / (6 + -7)')
53 -161.0
55 Raises ValueError if the expression is invalid, too long
56 or its computation involves too large values.
57 """
58 # Restrict the length of the expression to avoid potential Python crashes
59 # as per the documentation of ast.parse.
60 max_length = 30
61 if len(expr) > max_length:
62 raise ValueError(
63 f"Expression {expr[:max_length]!r}... is too long. "
64 f"Max length is {max_length}, got {len(expr)}."
65 )
66 try:
67 return eval_(ast.parse(expr, mode="eval").body)
68 except (TypeError, SyntaxError, OverflowError, KeyError, ZeroDivisionError) as e:
69 raise ValueError(
70 f"{expr!r} is not a valid or supported arithmetic expression."
71 ) from e
74def limit(max_=None):
75 """Return decorator that limits allowed returned values."""
77 def decorator(func):
78 @functools.wraps(func)
79 def wrapper(*args, **kwargs):
80 ret = func(*args, **kwargs)
81 try:
82 mag = abs(ret)
83 except TypeError:
84 pass # not applicable
85 else:
86 if mag > max_:
87 raise ValueError(
88 f"Numeric literal {ret} is too large, max is {max_}."
89 )
90 return ret
92 return wrapper
94 return decorator
97@limit(max_=MAX_VALUE)
98def eval_(node):
99 if isinstance(node, ast.Constant) and isinstance(node.value, (int, float)):
100 return node.value
101 elif isinstance(node, ast.BinOp): # <left> <operator> <right>
102 left = eval_(node.left)
103 right = eval_(node.right)
104 if isinstance(node.op, ast.Pow) and _pow_is_too_large(left, right):
105 raise ValueError(
106 f"Numeric literal {left}**{right} is too large, max is {MAX_VALUE}."
107 )
108 return operators[type(node.op)](left, right)
109 elif isinstance(node, ast.UnaryOp): # <operator> <operand> e.g., -1
110 return operators[type(node.op)](eval_(node.operand))
111 else:
112 raise TypeError(node)
115@dataclass(frozen=True)
116class _Sentinel:
117 """A sentinel to mark a parameter as not explicitly set"""
119 default_value: object
121 def __repr__(self):
122 return f"default({self.default_value!r})"
125class _TracebackCapturingWrapper:
126 """Protect function call and return error with traceback."""
128 def __init__(self, func):
129 self.func = func
131 def __call__(self, **kwargs):
132 try:
133 return self.func(**kwargs)
134 except BaseException as e:
135 return _ExceptionWithTraceback(e)
138def _retrieve_traceback_capturing_wrapped_call(out):
139 if isinstance(out, _ExceptionWithTraceback):
140 rebuild, args = out.__reduce__()
141 out = rebuild(*args)
142 if isinstance(out, BaseException):
143 raise out
144 return out