• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

zopefoundation / RestrictedPython / 27410799377

12 Jun 2026 10:44AM UTC coverage: 98.955% (-0.02%) from 98.972%
27410799377

Pull #317

github

web-flow
Merge e2eff83f1 into a2891c0d1
Pull Request #317: Type Annotations for RestrictedPython

212 of 219 branches covered (96.8%)

219 of 220 new or added lines in 8 files covered. (99.55%)

1 existing line in 1 file now uncovered.

2557 of 2584 relevant lines covered (98.96%)

0.99 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.75
/src/RestrictedPython/compile.py
1
from __future__ import annotations
1✔
2

3
import ast
1✔
4
import collections.abc
1✔
5
import os
1✔
6
import types
1✔
7
import typing
1✔
8
import warnings
1✔
9

10
from RestrictedPython._compat import IS_CPYTHON
1✔
11
from RestrictedPython._types import cast_not_none
1✔
12
from RestrictedPython.transformer import RestrictingNodeTransformer
1✔
13

14

15
# Temporary workaround for missing _typeshed
16
ReadableBuffer: typing.TypeAlias = bytes | bytearray
1✔
17

18

19
class CompileResult(typing.NamedTuple):
1✔
20
    code: types.CodeType | None
1✔
21
    errors: collections.abc.Sequence[str]
1✔
22
    warnings: collections.abc.Sequence[str]
1✔
23
    used_names: collections.abc.Mapping[str, bool]
1✔
24

25

26
syntax_error_template = (
1✔
27
    'Line {lineno}: {type}: {msg} at statement: {statement!r}'
28
)
29

30
NOT_CPYTHON_WARNING = (
1✔
31
    'RestrictedPython is only supported on CPython: use on other Python '
32
    'implementations may create security issues.'
33
)
34

35
_T_ast_compilable: typing.TypeAlias = (
1✔
36
    ast.Module | ast.Expression | ast.Interactive)
37
_T_source: typing.TypeAlias = str | ReadableBuffer | _T_ast_compilable
1✔
38

39

40
def _compile_restricted_mode(
1✔
41
        source: _T_source,
42
        filename: str | bytes | os.PathLike[typing.Any] = '<string>',
43
        mode: typing.Literal["exec", "eval", "single"] = "exec",
44
        flags: int = 0,
45
        dont_inherit: bool = False,
46
        policy: type[ast.NodeTransformer] | None = RestrictingNodeTransformer,
47
) -> CompileResult:
48

49
    if not IS_CPYTHON:
1✔
50
        warnings.warn_explicit(
1✔
51
            NOT_CPYTHON_WARNING, RuntimeWarning, 'RestrictedPython', 0)
52

53
    byte_code = None
1✔
54
    collected_errors: list[str] = []
1✔
55
    collected_warnings: list[str] = []
1✔
56
    used_names: dict[str, bool] = {}
1✔
57
    if policy is None:
1✔
58
        # Unrestricted Source Checks
59
        byte_code = compile(source, filename, mode=mode, flags=flags,
1✔
60
                            dont_inherit=dont_inherit)
61
    elif issubclass(policy, RestrictingNodeTransformer):
1✔
62
        allowed_source_types = [str, ast.Module]
1✔
63
        if not issubclass(type(source), tuple(allowed_source_types)):
1✔
64
            raise TypeError('Not allowed source type: '
1✔
65
                            '"{0.__class__.__name__}".'.format(source))
66
        c_ast: _T_ast_compilable | None = None
1✔
67
        # workaround for pypy issue https://bitbucket.org/pypy/pypy/issues/2552
68
        if isinstance(source, ast.Module):
1✔
69
            c_ast = source
1✔
70
        else:
71
            try:
1✔
72
                c_ast = typing.cast(
1✔
73
                    _T_ast_compilable, ast.parse(
74
                        source, filename, mode))
75
            except (TypeError, ValueError) as e:
1✔
UNCOV
76
                collected_errors.append(str(e))
×
77
            except SyntaxError as v:
1✔
78
                collected_errors.append(syntax_error_template.format(
1✔
79
                    lineno=v.lineno,
80
                    type=v.__class__.__name__,
81
                    msg=v.msg,
82
                    statement=v.text.strip() if v.text else None
83
                ))
84
        if c_ast:
1✔
85
            policy_instance = policy(
1✔
86
                collected_errors, collected_warnings, used_names)
87
            policy_instance.visit(c_ast)
1✔
88
            if not collected_errors:
1✔
89
                byte_code = compile(c_ast, filename, mode=mode  # ,
1✔
90
                                    # flags=flags,
91
                                    # dont_inherit=dont_inherit
92
                                    )
93
    else:
94
        raise TypeError('Unallowed policy provided for RestrictedPython')
1✔
95
    return CompileResult(
1✔
96
        byte_code,
97
        tuple(collected_errors),
98
        collected_warnings,
99
        used_names)
100

101

102
def compile_restricted_exec(
1✔
103
        source: _T_source,
104
        filename: str | bytes | os.PathLike[typing.Any] = '<string>',
105
        flags: int = 0,
106
        dont_inherit: bool = False,
107
        policy: type[ast.NodeTransformer] | None = RestrictingNodeTransformer,
108
) -> CompileResult:
109
    """Compile restricted for the mode `exec`."""
110
    return _compile_restricted_mode(
1✔
111
        source,
112
        filename=filename,
113
        mode='exec',
114
        flags=flags,
115
        dont_inherit=dont_inherit,
116
        policy=policy)
117

118

119
def compile_restricted_eval(
1✔
120
        source: _T_source,
121
        filename: str | bytes | os.PathLike[typing.Any] = '<string>',
122
        flags: int = 0,
123
        dont_inherit: bool = False,
124
        policy: type[ast.NodeTransformer] | None = RestrictingNodeTransformer,
125
) -> CompileResult:
126
    """Compile restricted for the mode `eval`."""
127
    return _compile_restricted_mode(
1✔
128
        source,
129
        filename=filename,
130
        mode='eval',
131
        flags=flags,
132
        dont_inherit=dont_inherit,
133
        policy=policy)
134

135

136
def compile_restricted_single(
1✔
137
        source: _T_source,
138
        filename: str | bytes | os.PathLike[typing.Any] = '<string>',
139
        flags: int = 0,
140
        dont_inherit: bool = False,
141
        policy: type[ast.NodeTransformer] | None = RestrictingNodeTransformer,
142
) -> CompileResult:
143
    """Compile restricted for the mode `single`."""
144
    return _compile_restricted_mode(
1✔
145
        source,
146
        filename=filename,
147
        mode='single',
148
        flags=flags,
149
        dont_inherit=dont_inherit,
150
        policy=policy)
151

152

153
def compile_restricted_function(
1✔
154
        p: str,  # parameters
155
        body: str | ReadableBuffer | ast.Module | ast.Interactive,
156
        name: str,
157
        filename: str | bytes | os.PathLike[typing.Any] = '<string>',
158
        # List of globals (e.g. ['here', 'context', ...])
159
        globalize: list[str] | None = None,
160
        flags: int = 0,
161
        dont_inherit: bool = False,
162
        policy: type[ast.NodeTransformer] | None = RestrictingNodeTransformer,
163
) -> CompileResult:
164
    """Compile a restricted code object for a function.
165

166
    Documentation see:
167
    http://restrictedpython.readthedocs.io/en/latest/usage/index.html#RestrictedPython.compile_restricted_function
168
    """
169
    # Parse the parameters and body, then combine them.
170
    try:
1✔
171
        body_ast = ast.parse(body, '<func code>', 'exec')
1✔
172
    except SyntaxError as v:
1✔
173
        error = syntax_error_template.format(
1✔
174
            lineno=v.lineno,
175
            type=v.__class__.__name__,
176
            msg=v.msg,
177
            statement=v.text.strip() if v.text else None)
178
        return CompileResult(
1✔
179
            code=None, errors=(error,), warnings=(), used_names={})
180

181
    # The compiled code is actually executed inside a function
182
    # (that is called when the code is called) so reading and assigning to a
183
    # global variable like this`printed += 'foo'` would throw an
184
    # UnboundLocalError.
185
    # We don't want the user to need to understand this.
186
    if globalize:
1✔
187
        body_ast.body.insert(0, ast.Global(globalize))
1✔
188
    wrapper_ast = ast.parse('def masked_function_name(%s): pass' % p,
1✔
189
                            '<func wrapper>', 'exec')
190
    # In case the name you chose for your generated function is not a
191
    # valid python identifier we set it after the fact
192
    function_ast = wrapper_ast.body[0]
1✔
193
    assert isinstance(function_ast, ast.FunctionDef)
1✔
194
    function_ast.name = name
1✔
195

196
    function_ast.body = body_ast.body
1✔
197
    wrapper_ast = ast.fix_missing_locations(wrapper_ast)
1✔
198

199
    result = _compile_restricted_mode(
1✔
200
        wrapper_ast,
201
        filename=filename,
202
        mode='exec',
203
        flags=flags,
204
        dont_inherit=dont_inherit,
205
        policy=policy)
206

207
    return result
1✔
208

209

210
def compile_restricted(
1✔
211
        source: _T_source,
212
        filename: str | bytes | os.PathLike[typing.Any] = '<unknown>',
213
        mode: str = 'exec',
214
        flags: int = 0,
215
        dont_inherit: bool = False,
216
        policy: type[ast.NodeTransformer] | None = RestrictingNodeTransformer,
217
) -> types.CodeType:
218
    """Replacement for the built-in compile() function.
219

220
    policy ... `ast.NodeTransformer` class defining the restrictions.
221

222
    """
223
    if mode in ['exec', 'eval', 'single', 'function']:
1✔
224
        result = _compile_restricted_mode(
1✔
225
            source,
226
            filename=filename,
227
            mode=mode,  # type: ignore[arg-type]
228
            # https://github.com/zopefoundation/RestrictedPython/issues/318
229
            flags=flags,
230
            dont_inherit=dont_inherit,
231
            policy=policy)
232
    else:
233
        raise TypeError('unknown mode %s', mode)
1✔
234
    for warning in result.warnings:
1✔
235
        warnings.warn(
1✔
236
            warning,
237
            SyntaxWarning
238
        )
239
    if result.errors:
1✔
240
        raise SyntaxError(result.errors)
1✔
241
    return cast_not_none(result.code)
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc