RestrictedPython 8.3__py3-none-any.whl → 8.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
RestrictedPython/Eval.py CHANGED
@@ -13,8 +13,12 @@
13
13
  """Restricted Python Expressions."""
14
14
 
15
15
  import ast
16
+ import collections
17
+ import types
18
+ import typing
16
19
 
17
- from .compile import compile_restricted_eval
20
+ from RestrictedPython._types import cast_not_none
21
+ from RestrictedPython.compile import compile_restricted_eval
18
22
 
19
23
 
20
24
  nltosp = str.maketrans('\r\n', ' ')
@@ -22,13 +26,21 @@ nltosp = str.maketrans('\r\n', ' ')
22
26
  # No restrictions.
23
27
  default_guarded_getattr = getattr
24
28
 
29
+ _T = typing.TypeVar('_T')
30
+ _TK = typing.TypeVar('_TK', contravariant=True)
31
+ _TV = typing.TypeVar('_TV', covariant=True)
25
32
 
26
- def default_guarded_getitem(ob, index):
33
+
34
+ class _GetItem(typing.Protocol[_TK, _TV]):
35
+ def __getitem__(self, key: _TK) -> _TV: ...
36
+
37
+
38
+ def default_guarded_getitem(ob: _GetItem[_TK, _TV], index: _TK) -> _TV:
27
39
  # No restrictions.
28
40
  return ob[index]
29
41
 
30
42
 
31
- def default_guarded_getiter(ob):
43
+ def default_guarded_getiter(ob: _T) -> _T:
32
44
  # No restrictions.
33
45
  return ob
34
46
 
@@ -36,17 +48,18 @@ def default_guarded_getiter(ob):
36
48
  class RestrictionCapableEval:
37
49
  """A base class for restricted code."""
38
50
 
39
- globals = {'__builtins__': None}
51
+ globals: dict[str, typing.Any] = {'__builtins__': None}
52
+
40
53
  # restricted
41
- rcode = None
54
+ rcode: types.CodeType | None = None
42
55
 
43
56
  # unrestricted
44
- ucode = None
57
+ ucode: types.CodeType | None = None
45
58
 
46
59
  # Names used by the expression
47
- used = None
60
+ used: tuple[str, ...] | None = None
48
61
 
49
- def __init__(self, expr):
62
+ def __init__(self, expr: str):
50
63
  """Create a restricted expression
51
64
 
52
65
  where:
@@ -60,7 +73,7 @@ class RestrictionCapableEval:
60
73
  # Catch syntax errors.
61
74
  self.prepUnrestrictedCode()
62
75
 
63
- def prepRestrictedCode(self):
76
+ def prepRestrictedCode(self) -> None:
64
77
  if self.rcode is None:
65
78
  result = compile_restricted_eval(self.expr, '<string>')
66
79
  if result.errors:
@@ -68,13 +81,12 @@ class RestrictionCapableEval:
68
81
  self.used = tuple(result.used_names)
69
82
  self.rcode = result.code
70
83
 
71
- def prepUnrestrictedCode(self):
84
+ def prepUnrestrictedCode(self) -> None:
72
85
  if self.ucode is None:
73
- exp_node = compile(
86
+ exp_node = ast.parse(
74
87
  self.expr,
75
88
  '<string>',
76
- 'eval',
77
- ast.PyCF_ONLY_AST)
89
+ 'eval')
78
90
 
79
91
  co = compile(exp_node, '<string>', 'eval')
80
92
 
@@ -90,7 +102,9 @@ class RestrictionCapableEval:
90
102
 
91
103
  self.ucode = co
92
104
 
93
- def eval(self, mapping):
105
+ def eval(self,
106
+ mapping: collections.abc.Mapping[str,
107
+ typing.Any]) -> typing.Any:
94
108
  # This default implementation is probably not very useful. :-(
95
109
  # This is meant to be overridden.
96
110
  self.prepRestrictedCode()
@@ -103,11 +117,11 @@ class RestrictionCapableEval:
103
117
 
104
118
  global_scope.update(self.globals)
105
119
 
106
- for name in self.used:
120
+ for name in cast_not_none(self.used):
107
121
  if (name not in global_scope) and (name in mapping):
108
122
  global_scope[name] = mapping[name]
109
123
 
110
- return eval(self.rcode, global_scope)
124
+ return eval(cast_not_none(self.rcode), global_scope)
111
125
 
112
- def __call__(self, **kw):
126
+ def __call__(self, **kw: typing.Any) -> typing.Any:
113
127
  return self.eval(kw)
@@ -16,6 +16,7 @@
16
16
  # DocumentTemplate.DT_UTil contains a few.
17
17
 
18
18
  import builtins
19
+ import string
19
20
 
20
21
  from RestrictedPython.transformer import INSPECT_ATTRIBUTES
21
22
 
@@ -220,7 +221,7 @@ def _full_write_guard():
220
221
  return guard
221
222
 
222
223
 
223
- full_write_guard = _full_write_guard()
224
+ full_write_guard = _full_write_guard() # type: ignore[no-untyped-call]
224
225
 
225
226
 
226
227
  def guarded_setattr(object, name, value):
@@ -238,6 +239,8 @@ safe_builtins['delattr'] = guarded_delattr
238
239
 
239
240
 
240
241
  raise_ = object()
242
+ _FORMATTER_UNSAFE_METHODS = frozenset(('format', 'get_field', 'get_value',
243
+ 'vformat'))
241
244
 
242
245
 
243
246
  def safer_getattr(object, name, default=None, getattr=getattr):
@@ -254,6 +257,14 @@ def safer_getattr(object, name, default=None, getattr=getattr):
254
257
  (isinstance(object, type) and issubclass(object, str))):
255
258
  raise NotImplementedError(
256
259
  'Using the format*() methods of `str` is not safe')
260
+ if object is string and name == 'Formatter':
261
+ raise NotImplementedError('string.Formatter is not safe')
262
+ if name in _FORMATTER_UNSAFE_METHODS and (
263
+ isinstance(object, string.Formatter) or
264
+ (isinstance(object, type) and
265
+ issubclass(object, string.Formatter))):
266
+ raise NotImplementedError(
267
+ 'Using string.Formatter methods is not safe')
257
268
  if name in INSPECT_ATTRIBUTES:
258
269
  raise AttributeError(
259
270
  f'"{name}" is a restricted name,'
@@ -10,11 +10,28 @@
10
10
  # FOR A PARTICULAR PURPOSE
11
11
  #
12
12
  ##############################################################################
13
+ import collections.abc
14
+ import typing
13
15
 
14
- limited_builtins = {}
15
16
 
17
+ limited_builtins: dict[str, typing.Any] = {}
16
18
 
17
- def limited_range(iFirst, *args):
19
+
20
+ @typing.overload
21
+ def limited_range(iFirst: int) -> collections.abc.Sequence[int]: ...
22
+
23
+
24
+ @typing.overload
25
+ def limited_range(iStart: int, iEnd: int, /
26
+ ) -> collections.abc.Sequence[int]: ...
27
+
28
+
29
+ @typing.overload
30
+ def limited_range(iStart: int, iEnd: int, iStep: int, /
31
+ ) -> collections.abc.Sequence[int]: ...
32
+
33
+
34
+ def limited_range(iFirst: int, *args: int) -> collections.abc.Sequence[int]:
18
35
  # limited range function from Martijn Pieters
19
36
  RANGELIMIT = 1000
20
37
  if not len(args):
@@ -41,8 +58,10 @@ def limited_range(iFirst, *args):
41
58
 
42
59
  limited_builtins['range'] = limited_range
43
60
 
61
+ _T = typing.TypeVar('_T')
62
+
44
63
 
45
- def limited_list(seq):
64
+ def limited_list(seq: collections.abc.Iterable[_T]) -> list[_T]:
46
65
  if isinstance(seq, str):
47
66
  raise TypeError('cannot convert string to list')
48
67
  return list(seq)
@@ -51,7 +70,7 @@ def limited_list(seq):
51
70
  limited_builtins['list'] = limited_list
52
71
 
53
72
 
54
- def limited_tuple(seq):
73
+ def limited_tuple(seq: collections.abc.Iterable[_T]) -> tuple[_T, ...]:
55
74
  if isinstance(seq, str):
56
75
  raise TypeError('cannot convert string to tuple')
57
76
  return tuple(seq)
@@ -15,17 +15,17 @@
15
15
  class PrintCollector:
16
16
  """Collect written text, and return it when called."""
17
17
 
18
- def __init__(self, _getattr_=None):
18
+ def __init__(self, _getattr_=None): # type: ignore[no-untyped-def]
19
19
  self.txt = []
20
20
  self._getattr_ = _getattr_
21
21
 
22
- def write(self, text):
22
+ def write(self, text: str) -> None:
23
23
  self.txt.append(text)
24
24
 
25
- def __call__(self):
25
+ def __call__(self) -> str:
26
26
  return ''.join(self.txt)
27
27
 
28
- def _call_print(self, *objects, **kwargs):
28
+ def _call_print(self, *objects, **kwargs): # type: ignore[no-untyped-def]
29
29
  if kwargs.get('file', None) is None:
30
30
  kwargs['file'] = self
31
31
  else:
@@ -11,21 +11,24 @@
11
11
  #
12
12
  ##############################################################################
13
13
 
14
+ import collections.abc
14
15
  import math
15
16
  import random
16
17
  import string
18
+ import types
19
+ import typing
17
20
 
18
21
 
19
- utility_builtins = {}
22
+ utility_builtins: dict[str, typing.Any] = {}
20
23
 
21
24
 
22
25
  class _AttributeDelegator:
23
- def __init__(self, mod, *excludes):
26
+ def __init__(self, mod: types.ModuleType, *excludes: str):
24
27
  """delegate attribute lookups outside *excludes* to module *mod*."""
25
28
  self.__mod = mod
26
29
  self.__excludes = excludes
27
30
 
28
- def __getattr__(self, attr):
31
+ def __getattr__(self, attr: str) -> typing.Any:
29
32
  if attr in self.__excludes:
30
33
  raise NotImplementedError(
31
34
  f"{self.__mod.__name__}.{attr} is not safe")
@@ -50,7 +53,7 @@ except ImportError:
50
53
  pass
51
54
 
52
55
 
53
- def same_type(arg1, *args):
56
+ def same_type(arg1: object, *args: object) -> bool:
54
57
  """Compares the class or type of two or more objects."""
55
58
  t = getattr(arg1, '__class__', type(arg1))
56
59
  for arg in args:
@@ -61,8 +64,10 @@ def same_type(arg1, *args):
61
64
 
62
65
  utility_builtins['same_type'] = same_type
63
66
 
67
+ _T = typing.TypeVar('_T')
64
68
 
65
- def test(*args):
69
+
70
+ def test(*args: _T) -> _T | None:
66
71
  length = len(args)
67
72
  for i in range(1, length, 2):
68
73
  if args[i - 1]:
@@ -70,12 +75,22 @@ def test(*args):
70
75
 
71
76
  if length % 2:
72
77
  return args[-1]
78
+ return None
73
79
 
74
80
 
75
81
  utility_builtins['test'] = test
76
82
 
83
+ _TK = typing.TypeVar('_TK')
84
+ _TV = typing.TypeVar('_TV')
85
+ _T_in: typing.TypeAlias = collections.abc.Iterable[_TK | tuple[_TK, _TV]]
86
+ _T_out: typing.TypeAlias = list[tuple[_TK, _TK | _TV]]
87
+
77
88
 
78
- def reorder(s, with_=None, without=()):
89
+ def reorder(
90
+ s: _T_in[_TK, _TV],
91
+ with_: collections.abc.Iterable[typing.Any] | None = None,
92
+ without: collections.abc.Iterable[typing.Any] = ()
93
+ ) -> _T_out[_TK, _TV]:
79
94
  # s, with_, and without are sequences treated as sets.
80
95
  # The result is subtract(intersect(s, with_), without),
81
96
  # unless with_ is None, in which case it is subtract(s, without).
@@ -0,0 +1,25 @@
1
+ import ast
2
+ import sys
3
+ import typing
4
+
5
+
6
+ _T = typing.TypeVar('_T')
7
+
8
+
9
+ def cast_not_none(var: _T | None) -> _T:
10
+ return typing.cast(_T, var)
11
+
12
+
13
+ # T_pos_ast are subtypes of ast.AST that have a position
14
+ # (have attributes: lineno, end_lineno, col_offset, and end_col_offset).
15
+ #
16
+ # ast.type_param is a new type in python 3.12 that has a position.
17
+ # TODO: Remove `else` when Support for Python 3.11 is dropped.
18
+ if sys.version_info >= (3, 12):
19
+ T_pos_ast: typing.TypeAlias = (
20
+ ast.stmt | ast.expr | ast.excepthandler | ast.arg | ast.keyword
21
+ | ast.alias | ast.pattern | ast.type_param)
22
+ else:
23
+ T_pos_ast: typing.TypeAlias = (
24
+ ast.stmt | ast.expr | ast.excepthandler | ast.arg | ast.keyword
25
+ | ast.alias | ast.pattern)
@@ -1,55 +1,84 @@
1
+ from __future__ import annotations
2
+
1
3
  import ast
4
+ import collections.abc
5
+ import os
6
+ import types
7
+ import typing
2
8
  import warnings
3
- from collections import namedtuple
4
9
 
5
10
  from RestrictedPython._compat import IS_CPYTHON
11
+ from RestrictedPython._types import cast_not_none
6
12
  from RestrictedPython.transformer import RestrictingNodeTransformer
13
+ from RestrictedPython.transformer import copy_locations
14
+
15
+
16
+ # Temporary workaround for missing _typeshed
17
+ ReadableBuffer: typing.TypeAlias = bytes | bytearray
18
+
19
+
20
+ class CompileResult(typing.NamedTuple):
21
+ code: types.CodeType | None
22
+ errors: collections.abc.Sequence[str]
23
+ warnings: collections.abc.Sequence[str]
24
+ used_names: collections.abc.Mapping[str, bool]
7
25
 
8
26
 
9
- CompileResult = namedtuple(
10
- 'CompileResult', 'code, errors, warnings, used_names')
11
27
  syntax_error_template = (
12
- 'Line {lineno}: {type}: {msg} at statement: {statement!r}')
28
+ 'Line {lineno}: {type}: {msg} at statement: {statement!r}'
29
+ )
13
30
 
14
31
  NOT_CPYTHON_WARNING = (
15
32
  'RestrictedPython is only supported on CPython: use on other Python '
16
33
  'implementations may create security issues.'
17
34
  )
18
35
 
36
+ _T_ast_compilable: typing.TypeAlias = (
37
+ ast.Module | ast.Expression | ast.Interactive)
38
+ _T_source: typing.TypeAlias = str | ReadableBuffer | _T_ast_compilable
39
+
19
40
 
20
41
  def _compile_restricted_mode(
21
- source,
22
- filename='<string>',
23
- mode="exec",
24
- flags=0,
25
- dont_inherit=False,
26
- policy=RestrictingNodeTransformer):
42
+ source: _T_source,
43
+ filename: str | bytes | os.PathLike[typing.Any] = '<string>',
44
+ mode: typing.Literal["exec", "eval", "single"] = "exec",
45
+ flags: int = 0,
46
+ dont_inherit: bool = False,
47
+ policy: type[ast.NodeTransformer] | None = RestrictingNodeTransformer,
48
+ ) -> CompileResult:
27
49
 
28
50
  if not IS_CPYTHON:
29
51
  warnings.warn_explicit(
30
52
  NOT_CPYTHON_WARNING, RuntimeWarning, 'RestrictedPython', 0)
31
53
 
32
54
  byte_code = None
33
- collected_errors = []
34
- collected_warnings = []
35
- used_names = {}
55
+ collected_errors: list[str] = []
56
+ collected_warnings: list[str] = []
57
+ used_names: dict[str, bool] = {}
36
58
  if policy is None:
37
59
  # Unrestricted Source Checks
38
60
  byte_code = compile(source, filename, mode=mode, flags=flags,
39
61
  dont_inherit=dont_inherit)
40
62
  elif issubclass(policy, RestrictingNodeTransformer):
41
- c_ast = None
42
- allowed_source_types = [str, ast.Module]
63
+ allowed_source_types = [
64
+ str,
65
+ bytes,
66
+ bytearray,
67
+ ast.Module,
68
+ ast.Expression,
69
+ ast.Interactive]
43
70
  if not issubclass(type(source), tuple(allowed_source_types)):
44
71
  raise TypeError('Not allowed source type: '
45
72
  '"{0.__class__.__name__}".'.format(source))
46
- c_ast = None
73
+ c_ast: _T_ast_compilable | None = None
47
74
  # workaround for pypy issue https://bitbucket.org/pypy/pypy/issues/2552
48
- if isinstance(source, ast.Module):
75
+ if isinstance(source, (ast.Module, ast.Expression, ast.Interactive)):
49
76
  c_ast = source
50
77
  else:
51
78
  try:
52
- c_ast = ast.parse(source, filename, mode)
79
+ c_ast = typing.cast(
80
+ _T_ast_compilable, ast.parse(
81
+ source, filename, mode))
53
82
  except (TypeError, ValueError) as e:
54
83
  collected_errors.append(str(e))
55
84
  except SyntaxError as v:
@@ -78,11 +107,12 @@ def _compile_restricted_mode(
78
107
 
79
108
 
80
109
  def compile_restricted_exec(
81
- source,
82
- filename='<string>',
83
- flags=0,
84
- dont_inherit=False,
85
- policy=RestrictingNodeTransformer):
110
+ source: _T_source,
111
+ filename: str | bytes | os.PathLike[typing.Any] = '<string>',
112
+ flags: int = 0,
113
+ dont_inherit: bool = False,
114
+ policy: type[ast.NodeTransformer] | None = RestrictingNodeTransformer,
115
+ ) -> CompileResult:
86
116
  """Compile restricted for the mode `exec`."""
87
117
  return _compile_restricted_mode(
88
118
  source,
@@ -94,11 +124,12 @@ def compile_restricted_exec(
94
124
 
95
125
 
96
126
  def compile_restricted_eval(
97
- source,
98
- filename='<string>',
99
- flags=0,
100
- dont_inherit=False,
101
- policy=RestrictingNodeTransformer):
127
+ source: _T_source,
128
+ filename: str | bytes | os.PathLike[typing.Any] = '<string>',
129
+ flags: int = 0,
130
+ dont_inherit: bool = False,
131
+ policy: type[ast.NodeTransformer] | None = RestrictingNodeTransformer,
132
+ ) -> CompileResult:
102
133
  """Compile restricted for the mode `eval`."""
103
134
  return _compile_restricted_mode(
104
135
  source,
@@ -110,11 +141,12 @@ def compile_restricted_eval(
110
141
 
111
142
 
112
143
  def compile_restricted_single(
113
- source,
114
- filename='<string>',
115
- flags=0,
116
- dont_inherit=False,
117
- policy=RestrictingNodeTransformer):
144
+ source: _T_source,
145
+ filename: str | bytes | os.PathLike[typing.Any] = '<string>',
146
+ flags: int = 0,
147
+ dont_inherit: bool = False,
148
+ policy: type[ast.NodeTransformer] | None = RestrictingNodeTransformer,
149
+ ) -> CompileResult:
118
150
  """Compile restricted for the mode `single`."""
119
151
  return _compile_restricted_mode(
120
152
  source,
@@ -126,30 +158,40 @@ def compile_restricted_single(
126
158
 
127
159
 
128
160
  def compile_restricted_function(
129
- p, # parameters
130
- body,
131
- name,
132
- filename='<string>',
133
- globalize=None, # List of globals (e.g. ['here', 'context', ...])
134
- flags=0,
135
- dont_inherit=False,
136
- policy=RestrictingNodeTransformer):
161
+ p: str, # parameters
162
+ body: _T_source,
163
+ name: str,
164
+ filename: str | bytes | os.PathLike[typing.Any] = '<string>',
165
+ # List of globals (e.g. ['here', 'context', ...])
166
+ globalize: list[str] | None = None,
167
+ flags: int = 0,
168
+ dont_inherit: bool = False,
169
+ policy: type[ast.NodeTransformer] | None = RestrictingNodeTransformer,
170
+ ) -> CompileResult:
137
171
  """Compile a restricted code object for a function.
138
172
 
139
173
  Documentation see:
140
174
  http://restrictedpython.readthedocs.io/en/latest/usage/index.html#RestrictedPython.compile_restricted_function
141
175
  """
142
176
  # Parse the parameters and body, then combine them.
143
- try:
144
- body_ast = ast.parse(body, '<func code>', 'exec')
145
- except SyntaxError as v:
146
- error = syntax_error_template.format(
147
- lineno=v.lineno,
148
- type=v.__class__.__name__,
149
- msg=v.msg,
150
- statement=v.text.strip() if v.text else None)
151
- return CompileResult(
152
- code=None, errors=(error,), warnings=(), used_names=())
177
+ body_ast: list[ast.stmt]
178
+ if isinstance(body, ast.Expression):
179
+ _body_ast = ast.Expr(body.body)
180
+ copy_locations(_body_ast, body.body)
181
+ body_ast = [_body_ast]
182
+ elif isinstance(body, (ast.Module, ast.Interactive)):
183
+ body_ast = body.body
184
+ else:
185
+ try:
186
+ body_ast = ast.parse(body, '<func code>', 'exec').body
187
+ except SyntaxError as v:
188
+ error = syntax_error_template.format(
189
+ lineno=v.lineno,
190
+ type=v.__class__.__name__,
191
+ msg=v.msg,
192
+ statement=v.text.strip() if v.text else None)
193
+ return CompileResult(
194
+ code=None, errors=(error,), warnings=(), used_names={})
153
195
 
154
196
  # The compiled code is actually executed inside a function
155
197
  # (that is called when the code is called) so reading and assigning to a
@@ -157,7 +199,7 @@ def compile_restricted_function(
157
199
  # UnboundLocalError.
158
200
  # We don't want the user to need to understand this.
159
201
  if globalize:
160
- body_ast.body.insert(0, ast.Global(globalize))
202
+ body_ast.insert(0, ast.Global(globalize))
161
203
  wrapper_ast = ast.parse('def masked_function_name(%s): pass' % p,
162
204
  '<func wrapper>', 'exec')
163
205
  # In case the name you chose for your generated function is not a
@@ -166,7 +208,7 @@ def compile_restricted_function(
166
208
  assert isinstance(function_ast, ast.FunctionDef)
167
209
  function_ast.name = name
168
210
 
169
- wrapper_ast.body[0].body = body_ast.body
211
+ function_ast.body = body_ast
170
212
  wrapper_ast = ast.fix_missing_locations(wrapper_ast)
171
213
 
172
214
  result = _compile_restricted_mode(
@@ -181,18 +223,19 @@ def compile_restricted_function(
181
223
 
182
224
 
183
225
  def compile_restricted(
184
- source,
185
- filename='<unknown>',
186
- mode='exec',
187
- flags=0,
188
- dont_inherit=False,
189
- policy=RestrictingNodeTransformer):
226
+ source: _T_source,
227
+ filename: str | bytes | os.PathLike[typing.Any] = '<unknown>',
228
+ mode: typing.Literal["exec", "eval", "single"] = 'exec',
229
+ flags: int = 0,
230
+ dont_inherit: bool = False,
231
+ policy: type[ast.NodeTransformer] | None = RestrictingNodeTransformer,
232
+ ) -> types.CodeType:
190
233
  """Replacement for the built-in compile() function.
191
234
 
192
235
  policy ... `ast.NodeTransformer` class defining the restrictions.
193
236
 
194
237
  """
195
- if mode in ['exec', 'eval', 'single', 'function']:
238
+ if mode in ['exec', 'eval', 'single']:
196
239
  result = _compile_restricted_mode(
197
240
  source,
198
241
  filename=filename,
@@ -209,4 +252,4 @@ def compile_restricted(
209
252
  )
210
253
  if result.errors:
211
254
  raise SyntaxError(result.errors)
212
- return result.code
255
+ return cast_not_none(result.code)
File without changes