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

zopefoundation / RestrictedPython / 27617568707

16 Jun 2026 12:29PM UTC coverage: 98.874% (-0.1%) from 98.976%
27617568707

Pull #321

github

web-flow
Merge f3c36af22 into f1b33e4cc
Pull Request #321: compile_restricted_function fix (docs+code)

212 of 215 branches covered (98.6%)

40 of 43 new or added lines in 2 files covered. (93.02%)

2546 of 2575 relevant lines covered (98.87%)

0.99 hits per line

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

94.74
/src/RestrictedPython/compile.py
1
import ast
1✔
2
import warnings
1✔
3
from collections import namedtuple
1✔
4

5
from RestrictedPython._compat import IS_CPYTHON
1✔
6
from RestrictedPython.transformer import RestrictingNodeTransformer
1✔
7
from RestrictedPython.transformer import copy_locations
1✔
8

9

10
CompileResult = namedtuple(
1✔
11
    'CompileResult', 'code, errors, warnings, used_names')
12
syntax_error_template = (
1✔
13
    'Line {lineno}: {type}: {msg} at statement: {statement!r}')
14

15
NOT_CPYTHON_WARNING = (
1✔
16
    'RestrictedPython is only supported on CPython: use on other Python '
17
    'implementations may create security issues.'
18
)
19

20

21
def _compile_restricted_mode(
1✔
22
        source,
23
        filename='<string>',
24
        mode="exec",
25
        flags=0,
26
        dont_inherit=False,
27
        policy=RestrictingNodeTransformer):
28

29
    if not IS_CPYTHON:
1✔
30
        warnings.warn_explicit(
1✔
31
            NOT_CPYTHON_WARNING, RuntimeWarning, 'RestrictedPython', 0)
32

33
    byte_code = None
1✔
34
    collected_errors = []
1✔
35
    collected_warnings = []
1✔
36
    used_names = {}
1✔
37
    if policy is None:
1✔
38
        # Unrestricted Source Checks
39
        byte_code = compile(source, filename, mode=mode, flags=flags,
1✔
40
                            dont_inherit=dont_inherit)
41
    elif issubclass(policy, RestrictingNodeTransformer):
1✔
42
        c_ast = None
1✔
43
        allowed_source_types = [str, ast.Module]
1✔
44
        if not issubclass(type(source), tuple(allowed_source_types)):
1✔
45
            raise TypeError('Not allowed source type: '
1✔
46
                            '"{0.__class__.__name__}".'.format(source))
47
        c_ast = None
1✔
48
        # workaround for pypy issue https://bitbucket.org/pypy/pypy/issues/2552
49
        if isinstance(source, ast.Module):
1✔
50
            c_ast = source
1✔
51
        else:
52
            try:
1✔
53
                c_ast = ast.parse(source, filename, mode)
1✔
54
            except (TypeError, ValueError) as e:
1✔
55
                collected_errors.append(str(e))
×
56
            except SyntaxError as v:
1✔
57
                collected_errors.append(syntax_error_template.format(
1✔
58
                    lineno=v.lineno,
59
                    type=v.__class__.__name__,
60
                    msg=v.msg,
61
                    statement=v.text.strip() if v.text else None
62
                ))
63
        if c_ast:
1✔
64
            policy_instance = policy(
1✔
65
                collected_errors, collected_warnings, used_names)
66
            policy_instance.visit(c_ast)
1✔
67
            if not collected_errors:
1✔
68
                byte_code = compile(c_ast, filename, mode=mode  # ,
1✔
69
                                    # flags=flags,
70
                                    # dont_inherit=dont_inherit
71
                                    )
72
    else:
73
        raise TypeError('Unallowed policy provided for RestrictedPython')
1✔
74
    return CompileResult(
1✔
75
        byte_code,
76
        tuple(collected_errors),
77
        collected_warnings,
78
        used_names)
79

80

81
def compile_restricted_exec(
1✔
82
        source,
83
        filename='<string>',
84
        flags=0,
85
        dont_inherit=False,
86
        policy=RestrictingNodeTransformer):
87
    """Compile restricted for the mode `exec`."""
88
    return _compile_restricted_mode(
1✔
89
        source,
90
        filename=filename,
91
        mode='exec',
92
        flags=flags,
93
        dont_inherit=dont_inherit,
94
        policy=policy)
95

96

97
def compile_restricted_eval(
1✔
98
        source,
99
        filename='<string>',
100
        flags=0,
101
        dont_inherit=False,
102
        policy=RestrictingNodeTransformer):
103
    """Compile restricted for the mode `eval`."""
104
    return _compile_restricted_mode(
1✔
105
        source,
106
        filename=filename,
107
        mode='eval',
108
        flags=flags,
109
        dont_inherit=dont_inherit,
110
        policy=policy)
111

112

113
def compile_restricted_single(
1✔
114
        source,
115
        filename='<string>',
116
        flags=0,
117
        dont_inherit=False,
118
        policy=RestrictingNodeTransformer):
119
    """Compile restricted for the mode `single`."""
120
    return _compile_restricted_mode(
1✔
121
        source,
122
        filename=filename,
123
        mode='single',
124
        flags=flags,
125
        dont_inherit=dont_inherit,
126
        policy=policy)
127

128

129
def compile_restricted_function(
1✔
130
        p,  # parameters
131
        body,
132
        name,
133
        filename='<string>',
134
        globalize=None,  # List of globals (e.g. ['here', 'context', ...])
135
        flags=0,
136
        dont_inherit=False,
137
        policy=RestrictingNodeTransformer):
138
    """Compile a restricted code object for a function.
139

140
    Documentation see:
141
    http://restrictedpython.readthedocs.io/en/latest/usage/index.html#RestrictedPython.compile_restricted_function
142
    """
143
    # Parse the parameters and body, then combine them.
144
    if isinstance(body, ast.Expression):
1!
NEW
145
        _body_ast = ast.Expr(body.body)
×
NEW
146
        copy_locations(_body_ast, body.body)
×
NEW
147
        body_ast = [_body_ast]
×
148
    elif isinstance(body, (ast.Module, ast.Interactive)):
1✔
149
        body_ast = body.body
1✔
150
    else:
151
        try:
1✔
152
            body_ast = ast.parse(body, '<func code>', 'exec').body
1✔
153
        except SyntaxError as v:
1✔
154
            error = syntax_error_template.format(
1✔
155
                lineno=v.lineno,
156
                type=v.__class__.__name__,
157
                msg=v.msg,
158
                statement=v.text.strip() if v.text else None)
159
            return CompileResult(
1✔
160
                code=None, errors=(error,), warnings=(), used_names=())
161

162
    # The compiled code is actually executed inside a function
163
    # (that is called when the code is called) so reading and assigning to a
164
    # global variable like this`printed += 'foo'` would throw an
165
    # UnboundLocalError.
166
    # We don't want the user to need to understand this.
167
    if globalize:
1✔
168
        body_ast.insert(0, ast.Global(globalize))
1✔
169
    wrapper_ast = ast.parse('def masked_function_name(%s): pass' % p,
1✔
170
                            '<func wrapper>', 'exec')
171
    # In case the name you chose for your generated function is not a
172
    # valid python identifier we set it after the fact
173
    function_ast = wrapper_ast.body[0]
1✔
174
    assert isinstance(function_ast, ast.FunctionDef)
1✔
175
    function_ast.name = name
1✔
176

177
    wrapper_ast.body[0].body = body_ast
1✔
178
    wrapper_ast = ast.fix_missing_locations(wrapper_ast)
1✔
179

180
    result = _compile_restricted_mode(
1✔
181
        wrapper_ast,
182
        filename=filename,
183
        mode='exec',
184
        flags=flags,
185
        dont_inherit=dont_inherit,
186
        policy=policy)
187

188
    return result
1✔
189

190

191
def compile_restricted(
1✔
192
        source,
193
        filename='<unknown>',
194
        mode='exec',
195
        flags=0,
196
        dont_inherit=False,
197
        policy=RestrictingNodeTransformer):
198
    """Replacement for the built-in compile() function.
199

200
    policy ... `ast.NodeTransformer` class defining the restrictions.
201

202
    """
203
    if mode in ['exec', 'eval', 'single', 'function']:
1✔
204
        result = _compile_restricted_mode(
1✔
205
            source,
206
            filename=filename,
207
            mode=mode,
208
            flags=flags,
209
            dont_inherit=dont_inherit,
210
            policy=policy)
211
    else:
212
        raise TypeError('unknown mode %s', mode)
1✔
213
    for warning in result.warnings:
1✔
214
        warnings.warn(
1✔
215
            warning,
216
            SyntaxWarning
217
        )
218
    if result.errors:
1✔
219
        raise SyntaxError(result.errors)
1✔
220
    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