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

zopefoundation / RestrictedPython / 26814889682

02 Jun 2026 10:49AM UTC coverage: 98.98% (+0.008%) from 98.972%
26814889682

Pull #317

github

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

207 of 209 branches covered (99.04%)

157 of 159 new or added lines in 7 files covered. (98.74%)

2 existing lines in 2 files now uncovered.

2523 of 2549 relevant lines covered (98.98%)

0.99 hits per line

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

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

3
import ast
1✔
4
import warnings
1✔
5
from ast import Expression
1✔
6
from ast import Interactive
1✔
7
from ast import Module
1✔
8
from ast import NodeTransformer
1✔
9
from collections.abc import Mapping
1✔
10
from collections.abc import Sequence
1✔
11
from os import PathLike
1✔
12
from types import CodeType
1✔
13
from typing import Any
1✔
14
from typing import Literal
1✔
15
from typing import NamedTuple
1✔
16
from typing import TypeAlias
1✔
17

18
from RestrictedPython._compat import IS_CPYTHON
1✔
19
from RestrictedPython.transformer import RestrictingNodeTransformer
1✔
20

21

22
# Temporary workaround for missing _typeshed
23
ReadableBuffer: TypeAlias = bytes | bytearray
1✔
24

25

26
class CompileResult(NamedTuple):
1✔
27
    code: CodeType | None
1✔
28
    errors: Sequence[str]
1✔
29
    warnings: Sequence[str]
1✔
30
    used_names: Mapping[str, bool]
1✔
31

32

33
syntax_error_template = (
1✔
34
    'Line {lineno}: {type}: {msg} at statement: {statement!r}')
35

36
NOT_CPYTHON_WARNING = (
1✔
37
    'RestrictedPython is only supported on CPython: use on other Python '
38
    'implementations may create security issues.'
39
)
40

41

42
def _compile_restricted_mode(
1✔
43
        source: str | ReadableBuffer | Module | Expression | Interactive,
44
        filename: str | ReadableBuffer | PathLike[Any] = '<string>',
45
        mode: Literal["exec", "eval", "single"] = "exec",
46
        flags: int = 0,
47
        dont_inherit: bool = False,
48
        policy: type[NodeTransformer] | None = RestrictingNodeTransformer,
49
) -> CompileResult:
50

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

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

102

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

119

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

136

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

153

154
def compile_restricted_function(
1✔
155
        p,  # parameters
156
        body,
157
        name: str,
158
        filename: str | ReadableBuffer | PathLike[Any] = '<string>',
159
        globalize=None,  # List of globals (e.g. ['here', 'context', ...])
160
        flags: int = 0,
161
        dont_inherit: bool = False,
162
        policy: type[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
    wrapper_ast.body[0].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: str | ReadableBuffer | Module | Expression | Interactive,
212
    filename: str | ReadableBuffer | PathLike[Any] = '<unknown>',
213
    mode: str = 'exec',
214
    flags: int = 0,
215
    dont_inherit: bool = False,
216
    policy: type[NodeTransformer] | None = RestrictingNodeTransformer,
217
) -> 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,
228
            flags=flags,
229
            dont_inherit=dont_inherit,
230
            policy=policy)
231
    else:
232
        raise TypeError('unknown mode %s', mode)
1✔
233
    for warning in result.warnings:
1✔
234
        warnings.warn(
1✔
235
            warning,
236
            SyntaxWarning
237
        )
238
    if result.errors:
1✔
239
        raise SyntaxError(result.errors)
1✔
240
    return 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