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

zopefoundation / RestrictedPython / 27617826978

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

push

github

web-flow
compile_restricted_function fix (docs+code) (#321)

* update-docs

* update-docs

* add support ast

* add tests

* update changelog

* small fix

---------

Co-authored-by: Jens Vagelpohl <jens@plyp.com>

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 = [
1✔
44
            str,
45
            bytes,
46
            bytearray,
47
            ast.Module,
48
            ast.Expression,
49
            ast.Interactive]
50
        if not issubclass(type(source), tuple(allowed_source_types)):
1✔
51
            raise TypeError('Not allowed source type: '
1✔
52
                            '"{0.__class__.__name__}".'.format(source))
53
        c_ast = None
1✔
54
        # workaround for pypy issue https://bitbucket.org/pypy/pypy/issues/2552
55
        if isinstance(source, (ast.Module, ast.Expression, ast.Interactive)):
1✔
56
            c_ast = source
1✔
57
        else:
58
            try:
1✔
59
                c_ast = ast.parse(source, filename, mode)
1✔
60
            except (TypeError, ValueError) as e:
1✔
61
                collected_errors.append(str(e))
×
62
            except SyntaxError as v:
1✔
63
                collected_errors.append(syntax_error_template.format(
1✔
64
                    lineno=v.lineno,
65
                    type=v.__class__.__name__,
66
                    msg=v.msg,
67
                    statement=v.text.strip() if v.text else None
68
                ))
69
        if c_ast:
1✔
70
            policy_instance = policy(
1✔
71
                collected_errors, collected_warnings, used_names)
72
            policy_instance.visit(c_ast)
1✔
73
            if not collected_errors:
1✔
74
                byte_code = compile(c_ast, filename, mode=mode  # ,
1✔
75
                                    # flags=flags,
76
                                    # dont_inherit=dont_inherit
77
                                    )
78
    else:
79
        raise TypeError('Unallowed policy provided for RestrictedPython')
1✔
80
    return CompileResult(
1✔
81
        byte_code,
82
        tuple(collected_errors),
83
        collected_warnings,
84
        used_names)
85

86

87
def compile_restricted_exec(
1✔
88
        source,
89
        filename='<string>',
90
        flags=0,
91
        dont_inherit=False,
92
        policy=RestrictingNodeTransformer):
93
    """Compile restricted for the mode `exec`."""
94
    return _compile_restricted_mode(
1✔
95
        source,
96
        filename=filename,
97
        mode='exec',
98
        flags=flags,
99
        dont_inherit=dont_inherit,
100
        policy=policy)
101

102

103
def compile_restricted_eval(
1✔
104
        source,
105
        filename='<string>',
106
        flags=0,
107
        dont_inherit=False,
108
        policy=RestrictingNodeTransformer):
109
    """Compile restricted for the mode `eval`."""
110
    return _compile_restricted_mode(
1✔
111
        source,
112
        filename=filename,
113
        mode='eval',
114
        flags=flags,
115
        dont_inherit=dont_inherit,
116
        policy=policy)
117

118

119
def compile_restricted_single(
1✔
120
        source,
121
        filename='<string>',
122
        flags=0,
123
        dont_inherit=False,
124
        policy=RestrictingNodeTransformer):
125
    """Compile restricted for the mode `single`."""
126
    return _compile_restricted_mode(
1✔
127
        source,
128
        filename=filename,
129
        mode='single',
130
        flags=flags,
131
        dont_inherit=dont_inherit,
132
        policy=policy)
133

134

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

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

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

183
    wrapper_ast.body[0].body = body_ast
1✔
184
    wrapper_ast = ast.fix_missing_locations(wrapper_ast)
1✔
185

186
    result = _compile_restricted_mode(
1✔
187
        wrapper_ast,
188
        filename=filename,
189
        mode='exec',
190
        flags=flags,
191
        dont_inherit=dont_inherit,
192
        policy=policy)
193

194
    return result
1✔
195

196

197
def compile_restricted(
1✔
198
        source,
199
        filename='<unknown>',
200
        mode='exec',
201
        flags=0,
202
        dont_inherit=False,
203
        policy=RestrictingNodeTransformer):
204
    """Replacement for the built-in compile() function.
205

206
    policy ... `ast.NodeTransformer` class defining the restrictions.
207

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