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

zopefoundation / RestrictedPython / 26870188116

03 Jun 2026 07:27AM UTC coverage: 98.978% (+0.006%) from 98.972%
26870188116

Pull #317

github

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

207 of 209 branches covered (99.04%)

153 of 154 new or added lines in 7 files covered. (99.35%)

3 existing lines in 2 files now uncovered.

2518 of 2544 relevant lines covered (98.98%)

0.99 hits per line

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

98.73
/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.transformer import RestrictingNodeTransformer
1✔
12

13

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

17

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

24

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

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

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

38

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

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

52
    byte_code = None
1✔
53
    collected_errors = []
1✔
54
    collected_warnings = []
1✔
55
    used_names = {}
1✔
56
    if policy is None:
1✔
57
        # Unrestricted Source Checks
58
        byte_code = compile(source, filename, mode=mode, flags=flags,
1✔
59
                            dont_inherit=dont_inherit)
60
    elif issubclass(policy, RestrictingNodeTransformer):
1✔
61
        c_ast = None
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 = 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 = ast.parse(source, filename, mode)
1✔
73
            except (TypeError, ValueError) as e:
1✔
NEW
74
                collected_errors.append(str(e))
×
75
            except SyntaxError as v:
1✔
76
                collected_errors.append(syntax_error_template.format(
1✔
77
                    lineno=v.lineno,
78
                    type=v.__class__.__name__,
79
                    msg=v.msg,
80
                    statement=v.text.strip() if v.text else None
81
                ))
82
        if c_ast:
1✔
83
            policy_instance = policy(
1✔
84
                collected_errors, collected_warnings, used_names)
85
            policy_instance.visit(c_ast)
1✔
86
            if not collected_errors:
1✔
87
                byte_code = compile(c_ast, filename, mode=mode  # ,
1✔
88
                                    # flags=flags,
89
                                    # dont_inherit=dont_inherit
90
                                    )
91
    else:
92
        raise TypeError('Unallowed policy provided for RestrictedPython')
1✔
93
    return CompileResult(
1✔
94
        byte_code,
95
        tuple(collected_errors),
96
        collected_warnings,
97
        used_names)
98

99

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

116

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

133

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

150

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

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

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

193
    wrapper_ast.body[0].body = body_ast.body
1✔
194
    wrapper_ast = ast.fix_missing_locations(wrapper_ast)
1✔
195

196
    result = _compile_restricted_mode(
1✔
197
        wrapper_ast,
198
        filename=filename,
199
        mode='exec',
200
        flags=flags,
201
        dont_inherit=dont_inherit,
202
        policy=policy)
203

204
    return result
1✔
205

206

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

217
    policy ... `ast.NodeTransformer` class defining the restrictions.
218

219
    """
220
    if mode in ['exec', 'eval', 'single', 'function']:
1✔
221
        result = _compile_restricted_mode(
1✔
222
            source,
223
            filename=filename,
224
            mode=mode,
225
            flags=flags,
226
            dont_inherit=dont_inherit,
227
            policy=policy)
228
    else:
229
        raise TypeError('unknown mode %s', mode)
1✔
230
    for warning in result.warnings:
1✔
231
        warnings.warn(
1✔
232
            warning,
233
            SyntaxWarning
234
        )
235
    if result.errors:
1✔
236
        raise SyntaxError(result.errors)
1✔
237
    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