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

zopefoundation / RestrictedPython / 18617392201

18 Oct 2025 03:17PM UTC coverage: 97.829% (-0.9%) from 98.772%
18617392201

Pull #303

github

loechel
readd Python 3.9 support
Pull Request #303: Type Annotations for RestrictedPython

215 of 235 branches covered (91.49%)

150 of 176 new or added lines in 5 files covered. (85.23%)

7 existing lines in 2 files now uncovered.

2524 of 2580 relevant lines covered (97.83%)

0.98 hits per line

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

96.39
/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 import namedtuple
1✔
10
from os import PathLike
1✔
11
from typing import Any
1✔
12
from typing import Literal
1✔
13

14
from RestrictedPython._compat import IS_CPYTHON
1✔
15
from RestrictedPython._compat import IS_PY310_OR_GREATER
1✔
16
from RestrictedPython.transformer import RestrictingNodeTransformer
1✔
17

18

19
if IS_PY310_OR_GREATER:
1!
20
    from typing import TypeAlias
1✔
21

22
    # Temporary workaround for missing _typeshed
23
    ReadableBuffer: TypeAlias = bytes | bytearray
1✔
24
else:
NEW
UNCOV
25
    from typing_extensions import TypeAlias  # type: ignore[import]
×
NEW
UNCOV
26
    ReadableBuffer: TypeAlias = bytes | bytearray  # type: ignore[no-redef]
×
27

28
CompileResult = namedtuple(
1✔
29
    'CompileResult', 'code, errors, warnings, used_names')
30
syntax_error_template = (
1✔
31
    'Line {lineno}: {type}: {msg} at statement: {statement!r}')
32

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

38

39
def _compile_restricted_mode(
1✔
40
        source: str | ReadableBuffer | Module | Expression | Interactive,
41
        filename: str | ReadableBuffer | PathLike[Any] = '<string>',
42
        mode: Literal["exec", "eval", "single"] = "exec",
43
        flags: int = 0,
44
        dont_inherit: bool = False,
45
        policy: NodeTransformer = 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, 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, 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✔
UNCOV
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: str | ReadableBuffer | Module | Expression | Interactive,
102
        filename: str | ReadableBuffer | PathLike[Any] = '<string>',
103
        flags: int = 0,
104
        dont_inherit: bool = False,
105
        policy: NodeTransformer = 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: str | ReadableBuffer | Module | Expression | Interactive,
119
        filename: str | ReadableBuffer | PathLike[Any] = '<string>',
120
        flags: int = 0,
121
        dont_inherit: bool = False,
122
        policy: NodeTransformer = 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: str | ReadableBuffer | Module | Expression | Interactive,
136
        filename: str | ReadableBuffer | PathLike[Any] = '<string>',
137
        flags: int = 0,
138
        dont_inherit: bool = False,
139
        policy: NodeTransformer = 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 | PathLike[Any] = '<string>',
156
        globalize=None,  # List of globals (e.g. ['here', 'context', ...])
157
        flags: int = 0,
158
        dont_inherit: bool = False,
159
        policy: ast.NodeTransformer = 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: str | ReadableBuffer | Module | Expression | Interactive,
209
    filename: str | ReadableBuffer | PathLike[Any] = '<unknown>',
210
    mode: str = 'exec',
211
    flags: int = 0,
212
    dont_inherit: bool = False,
213
    policy: NodeTransformer = RestrictingNodeTransformer,
214
) -> CompileResult:
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

© 2025 Coveralls, Inc