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

zopefoundation / RestrictedPython / 18613662438

18 Oct 2025 09:09AM UTC coverage: 98.778% (+0.006%) from 98.772%
18613662438

Pull #303

github

loechel
Remove License Cassifier, as they are deprecated
Pull Request #303: Type Annotations for RestrictedPython

213 of 231 branches covered (92.21%)

131 of 132 new or added lines in 4 files covered. (99.24%)

3 existing lines in 2 files now uncovered.

2505 of 2536 relevant lines covered (98.78%)

0.99 hits per line

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

98.72
/src/RestrictedPython/compile.py
1
import ast
1✔
2
import warnings
1✔
3
from ast import Expression
1✔
4
from ast import Interactive
1✔
5
from ast import Module
1✔
6
from ast import NodeTransformer
1✔
7
from collections import namedtuple
1✔
8
from os import PathLike
1✔
9
from typing import Any
1✔
10
from typing import Literal
1✔
11
from typing import TypeAlias
1✔
12

13
from RestrictedPython._compat import IS_CPYTHON
1✔
14
from RestrictedPython.transformer import RestrictingNodeTransformer
1✔
15

16

17
# Temporary workaround for missing _typeshed
18
ReadableBuffer: TypeAlias = bytes | bytearray
1✔
19

20

21
CompileResult = namedtuple(
1✔
22
    'CompileResult', 'code, errors, warnings, used_names')
23
syntax_error_template = (
1✔
24
    'Line {lineno}: {type}: {msg} at statement: {statement!r}')
25

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

31

32
def _compile_restricted_mode(
1✔
33
        source: str | ReadableBuffer | Module | Expression | Interactive,
34
        filename: str | ReadableBuffer | PathLike[Any] = '<string>',
35
        mode: Literal["exec", "eval", "single"] = "exec",
36
        flags: int = 0,
37
        dont_inherit: bool = False,
38
        policy: NodeTransformer = RestrictingNodeTransformer,
39
) -> CompileResult:
40

41
    if not IS_CPYTHON:
1✔
42
        warnings.warn_explicit(
1✔
43
            NOT_CPYTHON_WARNING, RuntimeWarning, 'RestrictedPython', 0)
44

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

92

93
def compile_restricted_exec(
1✔
94
        source: str | ReadableBuffer | Module | Expression | Interactive,
95
        filename: str | ReadableBuffer | PathLike[Any] = '<string>',
96
        flags: int = 0,
97
        dont_inherit: bool = False,
98
        policy: NodeTransformer = RestrictingNodeTransformer,
99
) -> CompileResult:
100
    """Compile restricted for the mode `exec`."""
101
    return _compile_restricted_mode(
1✔
102
        source,
103
        filename=filename,
104
        mode='exec',
105
        flags=flags,
106
        dont_inherit=dont_inherit,
107
        policy=policy)
108

109

110
def compile_restricted_eval(
1✔
111
        source: str | ReadableBuffer | Module | Expression | Interactive,
112
        filename: str | ReadableBuffer | PathLike[Any] = '<string>',
113
        flags: int = 0,
114
        dont_inherit: bool = False,
115
        policy: NodeTransformer = RestrictingNodeTransformer,
116
) -> CompileResult:
117
    """Compile restricted for the mode `eval`."""
118
    return _compile_restricted_mode(
1✔
119
        source,
120
        filename=filename,
121
        mode='eval',
122
        flags=flags,
123
        dont_inherit=dont_inherit,
124
        policy=policy)
125

126

127
def compile_restricted_single(
1✔
128
        source: str | ReadableBuffer | Module | Expression | Interactive,
129
        filename: str | ReadableBuffer | PathLike[Any] = '<string>',
130
        flags: int = 0,
131
        dont_inherit: bool = False,
132
        policy: NodeTransformer = RestrictingNodeTransformer,
133
) -> CompileResult:
134
    """Compile restricted for the mode `single`."""
135
    return _compile_restricted_mode(
1✔
136
        source,
137
        filename=filename,
138
        mode='single',
139
        flags=flags,
140
        dont_inherit=dont_inherit,
141
        policy=policy)
142

143

144
def compile_restricted_function(
1✔
145
        p,  # parameters
146
        body,
147
        name: str,
148
        filename: str | ReadableBuffer | PathLike[Any] = '<string>',
149
        globalize=None,  # List of globals (e.g. ['here', 'context', ...])
150
        flags: int = 0,
151
        dont_inherit: bool = False,
152
        policy: ast.NodeTransformer = RestrictingNodeTransformer,
153
) -> CompileResult:
154
    """Compile a restricted code object for a function.
155

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

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

186
    wrapper_ast.body[0].body = body_ast.body
1✔
187
    wrapper_ast = ast.fix_missing_locations(wrapper_ast)
1✔
188

189
    result = _compile_restricted_mode(
1✔
190
        wrapper_ast,
191
        filename=filename,
192
        mode='exec',
193
        flags=flags,
194
        dont_inherit=dont_inherit,
195
        policy=policy)
196

197
    return result
1✔
198

199

200
def compile_restricted(
1✔
201
    source: str | ReadableBuffer | Module | Expression | Interactive,
202
    filename: str | ReadableBuffer | PathLike[Any] = '<unknown>',
203
    mode: str = 'exec',
204
    flags: int = 0,
205
    dont_inherit: bool = False,
206
    policy: NodeTransformer = RestrictingNodeTransformer,
207
) -> CompileResult:
208
    """Replacement for the built-in compile() function.
209

210
    policy ... `ast.NodeTransformer` class defining the restrictions.
211

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