RestrictedPython 8.3__tar.gz → 8.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. {restrictedpython-8.3 → restrictedpython-8.4}/.pre-commit-config.yaml +5 -0
  2. {restrictedpython-8.3 → restrictedpython-8.4}/CHANGES.rst +13 -0
  3. {restrictedpython-8.3 → restrictedpython-8.4}/PKG-INFO +17 -1
  4. {restrictedpython-8.3 → restrictedpython-8.4}/docs/usage/api.rst +8 -8
  5. {restrictedpython-8.3 → restrictedpython-8.4}/pyproject.toml +28 -1
  6. {restrictedpython-8.3 → restrictedpython-8.4}/src/RestrictedPython/Eval.py +31 -17
  7. {restrictedpython-8.3 → restrictedpython-8.4}/src/RestrictedPython/Guards.py +12 -1
  8. {restrictedpython-8.3 → restrictedpython-8.4}/src/RestrictedPython/Limits.py +23 -4
  9. {restrictedpython-8.3 → restrictedpython-8.4}/src/RestrictedPython/PrintCollector.py +4 -4
  10. {restrictedpython-8.3 → restrictedpython-8.4}/src/RestrictedPython/Utilities.py +21 -6
  11. restrictedpython-8.4/src/RestrictedPython/_types.py +25 -0
  12. {restrictedpython-8.3 → restrictedpython-8.4}/src/RestrictedPython/compile.py +104 -61
  13. restrictedpython-8.4/src/RestrictedPython/py.typed +0 -0
  14. {restrictedpython-8.3 → restrictedpython-8.4}/src/RestrictedPython/transformer.py +175 -127
  15. {restrictedpython-8.3 → restrictedpython-8.4}/src/RestrictedPython.egg-info/PKG-INFO +17 -1
  16. {restrictedpython-8.3 → restrictedpython-8.4}/src/RestrictedPython.egg-info/SOURCES.txt +2 -0
  17. {restrictedpython-8.3 → restrictedpython-8.4}/src/RestrictedPython.egg-info/requires.txt +3 -0
  18. {restrictedpython-8.3 → restrictedpython-8.4}/tests/test_Guards.py +133 -0
  19. {restrictedpython-8.3 → restrictedpython-8.4}/tests/test_compile_restricted_function.py +66 -0
  20. {restrictedpython-8.3 → restrictedpython-8.4}/.readthedocs.yaml +0 -0
  21. {restrictedpython-8.3 → restrictedpython-8.4}/CONTRIBUTING.md +0 -0
  22. {restrictedpython-8.3 → restrictedpython-8.4}/COPYRIGHT.txt +0 -0
  23. {restrictedpython-8.3 → restrictedpython-8.4}/LICENSE.txt +0 -0
  24. {restrictedpython-8.3 → restrictedpython-8.4}/MANIFEST.in +0 -0
  25. {restrictedpython-8.3 → restrictedpython-8.4}/README.rst +0 -0
  26. {restrictedpython-8.3 → restrictedpython-8.4}/buildout.cfg +0 -0
  27. {restrictedpython-8.3 → restrictedpython-8.4}/constraints.txt +0 -0
  28. {restrictedpython-8.3 → restrictedpython-8.4}/docs/Makefile +0 -0
  29. {restrictedpython-8.3 → restrictedpython-8.4}/docs/changes.rst +0 -0
  30. {restrictedpython-8.3 → restrictedpython-8.4}/docs/conf.py +0 -0
  31. {restrictedpython-8.3 → restrictedpython-8.4}/docs/contributing/ast/python3_10.ast +0 -0
  32. {restrictedpython-8.3 → restrictedpython-8.4}/docs/contributing/ast/python3_11.ast +0 -0
  33. {restrictedpython-8.3 → restrictedpython-8.4}/docs/contributing/ast/python3_12.ast +0 -0
  34. {restrictedpython-8.3 → restrictedpython-8.4}/docs/contributing/ast/python3_13.ast +0 -0
  35. {restrictedpython-8.3 → restrictedpython-8.4}/docs/contributing/ast/python3_14.ast +0 -0
  36. {restrictedpython-8.3 → restrictedpython-8.4}/docs/contributing/ast/python3_8.ast +0 -0
  37. {restrictedpython-8.3 → restrictedpython-8.4}/docs/contributing/ast/python3_9.ast +0 -0
  38. {restrictedpython-8.3 → restrictedpython-8.4}/docs/contributing/changes_from310to311.rst +0 -0
  39. {restrictedpython-8.3 → restrictedpython-8.4}/docs/contributing/changes_from311to312.rst +0 -0
  40. {restrictedpython-8.3 → restrictedpython-8.4}/docs/contributing/changes_from312to313.rst +0 -0
  41. {restrictedpython-8.3 → restrictedpython-8.4}/docs/contributing/changes_from313to314.rst +0 -0
  42. {restrictedpython-8.3 → restrictedpython-8.4}/docs/contributing/changes_from39to310.rst +0 -0
  43. {restrictedpython-8.3 → restrictedpython-8.4}/docs/contributing/index.rst +0 -0
  44. {restrictedpython-8.3 → restrictedpython-8.4}/docs/idea.rst +0 -0
  45. {restrictedpython-8.3 → restrictedpython-8.4}/docs/index.rst +0 -0
  46. {restrictedpython-8.3 → restrictedpython-8.4}/docs/install/index.rst +0 -0
  47. {restrictedpython-8.3 → restrictedpython-8.4}/docs/logo.jpg +0 -0
  48. {restrictedpython-8.3 → restrictedpython-8.4}/docs/make.bat +0 -0
  49. {restrictedpython-8.3 → restrictedpython-8.4}/docs/requirements.txt +0 -0
  50. {restrictedpython-8.3 → restrictedpython-8.4}/docs/roadmap/index.rst +0 -0
  51. {restrictedpython-8.3 → restrictedpython-8.4}/docs/usage/basic_usage.rst +0 -0
  52. {restrictedpython-8.3 → restrictedpython-8.4}/docs/usage/framework_usage.rst +0 -0
  53. {restrictedpython-8.3 → restrictedpython-8.4}/docs/usage/index.rst +0 -0
  54. {restrictedpython-8.3 → restrictedpython-8.4}/docs/usage/policy.rst +0 -0
  55. {restrictedpython-8.3 → restrictedpython-8.4}/setup.cfg +0 -0
  56. {restrictedpython-8.3 → restrictedpython-8.4}/setup.py +0 -0
  57. {restrictedpython-8.3 → restrictedpython-8.4}/src/RestrictedPython/__init__.py +0 -0
  58. {restrictedpython-8.3 → restrictedpython-8.4}/src/RestrictedPython/_compat.py +0 -0
  59. {restrictedpython-8.3 → restrictedpython-8.4}/src/RestrictedPython.egg-info/dependency_links.txt +0 -0
  60. {restrictedpython-8.3 → restrictedpython-8.4}/src/RestrictedPython.egg-info/top_level.txt +0 -0
  61. {restrictedpython-8.3 → restrictedpython-8.4}/tests/__init__.py +0 -0
  62. {restrictedpython-8.3 → restrictedpython-8.4}/tests/builtins/test_limits.py +0 -0
  63. {restrictedpython-8.3 → restrictedpython-8.4}/tests/builtins/test_utilities.py +0 -0
  64. {restrictedpython-8.3 → restrictedpython-8.4}/tests/helper.py +0 -0
  65. {restrictedpython-8.3 → restrictedpython-8.4}/tests/test_NamedExpr.py +0 -0
  66. {restrictedpython-8.3 → restrictedpython-8.4}/tests/test_Utilities.py +0 -0
  67. {restrictedpython-8.3 → restrictedpython-8.4}/tests/test_compile.py +0 -0
  68. {restrictedpython-8.3 → restrictedpython-8.4}/tests/test_eval.py +0 -0
  69. {restrictedpython-8.3 → restrictedpython-8.4}/tests/test_imports.py +0 -0
  70. {restrictedpython-8.3 → restrictedpython-8.4}/tests/test_iterating_over_dict_items.py +0 -0
  71. {restrictedpython-8.3 → restrictedpython-8.4}/tests/test_print_function.py +0 -0
  72. {restrictedpython-8.3 → restrictedpython-8.4}/tests/transformer/operators/test_arithmetic_operators.py +0 -0
  73. {restrictedpython-8.3 → restrictedpython-8.4}/tests/transformer/operators/test_bit_wise_operators.py +0 -0
  74. {restrictedpython-8.3 → restrictedpython-8.4}/tests/transformer/operators/test_bool_operators.py +0 -0
  75. {restrictedpython-8.3 → restrictedpython-8.4}/tests/transformer/operators/test_comparison_operators.py +0 -0
  76. {restrictedpython-8.3 → restrictedpython-8.4}/tests/transformer/operators/test_identity_operators.py +0 -0
  77. {restrictedpython-8.3 → restrictedpython-8.4}/tests/transformer/operators/test_logical_operators.py +0 -0
  78. {restrictedpython-8.3 → restrictedpython-8.4}/tests/transformer/operators/test_unary_operators.py +0 -0
  79. {restrictedpython-8.3 → restrictedpython-8.4}/tests/transformer/test_assert.py +0 -0
  80. {restrictedpython-8.3 → restrictedpython-8.4}/tests/transformer/test_assign.py +0 -0
  81. {restrictedpython-8.3 → restrictedpython-8.4}/tests/transformer/test_async.py +0 -0
  82. {restrictedpython-8.3 → restrictedpython-8.4}/tests/transformer/test_attribute.py +0 -0
  83. {restrictedpython-8.3 → restrictedpython-8.4}/tests/transformer/test_augassign.py +0 -0
  84. {restrictedpython-8.3 → restrictedpython-8.4}/tests/transformer/test_base_types.py +0 -0
  85. {restrictedpython-8.3 → restrictedpython-8.4}/tests/transformer/test_breakpoint.py +0 -0
  86. {restrictedpython-8.3 → restrictedpython-8.4}/tests/transformer/test_call.py +0 -0
  87. {restrictedpython-8.3 → restrictedpython-8.4}/tests/transformer/test_classdef.py +0 -0
  88. {restrictedpython-8.3 → restrictedpython-8.4}/tests/transformer/test_comparators.py +0 -0
  89. {restrictedpython-8.3 → restrictedpython-8.4}/tests/transformer/test_conditional.py +0 -0
  90. {restrictedpython-8.3 → restrictedpython-8.4}/tests/transformer/test_dict_comprehension.py +0 -0
  91. {restrictedpython-8.3 → restrictedpython-8.4}/tests/transformer/test_eval_exec.py +0 -0
  92. {restrictedpython-8.3 → restrictedpython-8.4}/tests/transformer/test_fstring.py +0 -0
  93. {restrictedpython-8.3 → restrictedpython-8.4}/tests/transformer/test_functiondef.py +0 -0
  94. {restrictedpython-8.3 → restrictedpython-8.4}/tests/transformer/test_gen.py +0 -0
  95. {restrictedpython-8.3 → restrictedpython-8.4}/tests/transformer/test_generic.py +0 -0
  96. {restrictedpython-8.3 → restrictedpython-8.4}/tests/transformer/test_global_local.py +0 -0
  97. {restrictedpython-8.3 → restrictedpython-8.4}/tests/transformer/test_import.py +0 -0
  98. {restrictedpython-8.3 → restrictedpython-8.4}/tests/transformer/test_inspect.py +0 -0
  99. {restrictedpython-8.3 → restrictedpython-8.4}/tests/transformer/test_iterator.py +0 -0
  100. {restrictedpython-8.3 → restrictedpython-8.4}/tests/transformer/test_lambda.py +0 -0
  101. {restrictedpython-8.3 → restrictedpython-8.4}/tests/transformer/test_loop.py +0 -0
  102. {restrictedpython-8.3 → restrictedpython-8.4}/tests/transformer/test_name.py +0 -0
  103. {restrictedpython-8.3 → restrictedpython-8.4}/tests/transformer/test_slice.py +0 -0
  104. {restrictedpython-8.3 → restrictedpython-8.4}/tests/transformer/test_subscript.py +0 -0
  105. {restrictedpython-8.3 → restrictedpython-8.4}/tests/transformer/test_try.py +0 -0
  106. {restrictedpython-8.3 → restrictedpython-8.4}/tests/transformer/test_tstring.py +0 -0
  107. {restrictedpython-8.3 → restrictedpython-8.4}/tests/transformer/test_with_stmt.py +0 -0
  108. {restrictedpython-8.3 → restrictedpython-8.4}/tests/transformer/test_yield.py +0 -0
  109. {restrictedpython-8.3 → restrictedpython-8.4}/tox.ini +0 -0
@@ -27,3 +27,8 @@ repos:
27
27
  - id: flake8
28
28
  additional_dependencies:
29
29
  - flake8-debugger == 4.1.2
30
+ - repo: https://github.com/pre-commit/mirrors-mypy
31
+ rev: v2.1.0
32
+ hooks:
33
+ - id: mypy
34
+ pass_filenames: false
@@ -1,6 +1,19 @@
1
1
  Changes
2
2
  =======
3
3
 
4
+ 8.4 (2026-07-10)
5
+ ----------------
6
+
7
+ - Add type annotations to the package code.
8
+ For clarification, restricted Python code does not support type annotations.
9
+
10
+ - Allow ``ast.Module``, ``ast.Expression`` and ``ast.Interactive`` as body in compile_restricted_function
11
+
12
+ - Disallow ``mode="function"`` in ``compile_restricted`` (it never worked).
13
+
14
+ - Prevent access to ``string.Formatter`` and its unsafe traversal methods via
15
+ ``safer_getattr``.
16
+
4
17
  8.3 (2026-06-16)
5
18
  ----------------
6
19
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: RestrictedPython
3
- Version: 8.3
3
+ Version: 8.4
4
4
  Summary: RestrictedPython is a defined subset of the Python language which allows to provide a program input into a trusted environment.
5
5
  Author-email: Zope Foundation and contributors <zope-dev@zope.dev>
6
6
  Maintainer-email: Plone Foundation and contributors <zope-dev@zope.dev>
@@ -21,6 +21,7 @@ Classifier: Programming Language :: Python :: 3.13
21
21
  Classifier: Programming Language :: Python :: 3.14
22
22
  Classifier: Programming Language :: Python :: Implementation :: CPython
23
23
  Classifier: Topic :: Security
24
+ Classifier: Typing :: Typed
24
25
  Requires-Python: <3.16,>=3.10
25
26
  Description-Content-Type: text/x-rst
26
27
  License-File: LICENSE.txt
@@ -30,6 +31,8 @@ Requires-Dist: pytest-mock; extra == "test"
30
31
  Provides-Extra: docs
31
32
  Requires-Dist: Sphinx; extra == "docs"
32
33
  Requires-Dist: furo; extra == "docs"
34
+ Provides-Extra: typecheck
35
+ Requires-Dist: mypy; extra == "typecheck"
33
36
  Dynamic: license-file
34
37
 
35
38
  .. image:: https://github.com/zopefoundation/RestrictedPython/actions/workflows/tests.yml/badge.svg
@@ -123,6 +126,19 @@ the documentation `Contributing page
123
126
  Changes
124
127
  =======
125
128
 
129
+ 8.4 (2026-07-10)
130
+ ----------------
131
+
132
+ - Add type annotations to the package code.
133
+ For clarification, restricted Python code does not support type annotations.
134
+
135
+ - Allow ``ast.Module``, ``ast.Expression`` and ``ast.Interactive`` as body in compile_restricted_function
136
+
137
+ - Disallow ``mode="function"`` in ``compile_restricted`` (it never worked).
138
+
139
+ - Prevent access to ``string.Formatter`` and its unsafe traversal methods via
140
+ ``safer_getattr``.
141
+
126
142
  8.3 (2026-06-16)
127
143
  ----------------
128
144
 
@@ -11,13 +11,13 @@ API overview
11
11
 
12
12
  :param source: (required). the source code that should be compiled
13
13
  :param filename: (optional). defaults to ``'<unknown>'``
14
- :param mode: (optional). Use ``'exec'``, ``'eval'``, ``'single'`` or ``'function'``. defaults to ``'exec'``
14
+ :param mode: (optional). Use ``'exec'``, ``'eval'`` or ``'single'``. defaults to ``'exec'``
15
15
  :param flags: (optional). defaults to ``0``
16
16
  :param dont_inherit: (optional). defaults to ``False``
17
17
  :param policy: (optional). defaults to ``RestrictingNodeTransformer``
18
- :type source: str or unicode text or ``ast.AST``
19
- :type filename: str or unicode text
20
- :type mode: str or unicode text
18
+ :type source: str or bytes or bytearray or ``ast.Module`` or ``ast.Expression`` or ``ast.Interactive``
19
+ :type filename: str or bytes or os.PathLike[typing.Any]
20
+ :type mode: str
21
21
  :type flags: int
22
22
  :type dont_inherit: int
23
23
  :type policy: RestrictingNodeTransformer class
@@ -67,10 +67,10 @@ API overview
67
67
  :param flags: (optional). defaults to ``0``
68
68
  :param dont_inherit: (optional). defaults to ``False``
69
69
  :param policy: (optional). defaults to ``RestrictingNodeTransformer``
70
- :type p: str or unicode text
71
- :type body: str or unicode text
72
- :type name: str or unicode text
73
- :type filename: str or unicode text
70
+ :type p: str
71
+ :type body: str or bytes or bytearray or ``ast.Module`` or ``ast.Expression`` or ``ast.Interactive``
72
+ :type name: str
73
+ :type filename: str or bytes or os.PathLike[typing.Any]
74
74
  :type globalize: None or list
75
75
  :type flags: int
76
76
  :type dont_inherit: int
@@ -9,7 +9,7 @@ build-backend = "setuptools.build_meta"
9
9
 
10
10
  [project]
11
11
  name = "RestrictedPython"
12
- version = "8.3"
12
+ version = "8.4"
13
13
  description = "RestrictedPython is a defined subset of the Python language which allows to provide a program input into a trusted environment."
14
14
  license = "ZPL-2.1"
15
15
  classifiers = [
@@ -24,6 +24,7 @@ classifiers = [
24
24
  "Programming Language :: Python :: 3.14",
25
25
  "Programming Language :: Python :: Implementation :: CPython",
26
26
  "Topic :: Security",
27
+ "Typing :: Typed",
27
28
  ]
28
29
  dynamic = ["readme"]
29
30
  requires-python = ">=3.10, <3.16"
@@ -50,6 +51,9 @@ docs = [
50
51
  "Sphinx",
51
52
  "furo",
52
53
  ]
54
+ typecheck = [
55
+ "mypy",
56
+ ]
53
57
 
54
58
  [project.urls]
55
59
  Documentation = "https://restrictedpython.readthedocs.io/"
@@ -57,6 +61,7 @@ Issues = "https://github.com/zopefoundation/RestrictedPython/issues"
57
61
  Source = "https://github.com/zopefoundation/RestrictedPython"
58
62
  Changelog = "https://github.com/zopefoundation/RestrictedPython/blob/master/CHANGES.rst"
59
63
 
64
+
60
65
  [tool.coverage.run]
61
66
  branch = true
62
67
  source = ["RestrictedPython"]
@@ -84,6 +89,28 @@ directory = "parts/htmlcov"
84
89
  readme = {file = ["README.rst", "CHANGES.rst"]}
85
90
 
86
91
 
92
+ [tool.mypy]
93
+ mypy_path = "src"
94
+ packages = ["RestrictedPython"]
95
+ python_version = "3.10"
96
+ warn_unreachable = true
97
+ implicit_reexport = false
98
+ strict = true
99
+
100
+ [[tool.mypy.overrides]]
101
+ module = ["DateTime"]
102
+ ignore_missing_imports = true
103
+
104
+ [[tool.mypy.overrides]]
105
+ module = ["RestrictedPython.Guards"]
106
+ check_untyped_defs = false
107
+ disallow_untyped_defs = false
108
+
109
+ [[tool.mypy.overrides]]
110
+ module = ["RestrictedPython.transformer"]
111
+ warn_no_return = false
112
+
113
+
87
114
  [tool.zest-releaser]
88
115
  create-wheel = false
89
116
  upload-pypi = false
@@ -13,8 +13,12 @@
13
13
  """Restricted Python Expressions."""
14
14
 
15
15
  import ast
16
+ import collections
17
+ import types
18
+ import typing
16
19
 
17
- from .compile import compile_restricted_eval
20
+ from RestrictedPython._types import cast_not_none
21
+ from RestrictedPython.compile import compile_restricted_eval
18
22
 
19
23
 
20
24
  nltosp = str.maketrans('\r\n', ' ')
@@ -22,13 +26,21 @@ nltosp = str.maketrans('\r\n', ' ')
22
26
  # No restrictions.
23
27
  default_guarded_getattr = getattr
24
28
 
29
+ _T = typing.TypeVar('_T')
30
+ _TK = typing.TypeVar('_TK', contravariant=True)
31
+ _TV = typing.TypeVar('_TV', covariant=True)
25
32
 
26
- def default_guarded_getitem(ob, index):
33
+
34
+ class _GetItem(typing.Protocol[_TK, _TV]):
35
+ def __getitem__(self, key: _TK) -> _TV: ...
36
+
37
+
38
+ def default_guarded_getitem(ob: _GetItem[_TK, _TV], index: _TK) -> _TV:
27
39
  # No restrictions.
28
40
  return ob[index]
29
41
 
30
42
 
31
- def default_guarded_getiter(ob):
43
+ def default_guarded_getiter(ob: _T) -> _T:
32
44
  # No restrictions.
33
45
  return ob
34
46
 
@@ -36,17 +48,18 @@ def default_guarded_getiter(ob):
36
48
  class RestrictionCapableEval:
37
49
  """A base class for restricted code."""
38
50
 
39
- globals = {'__builtins__': None}
51
+ globals: dict[str, typing.Any] = {'__builtins__': None}
52
+
40
53
  # restricted
41
- rcode = None
54
+ rcode: types.CodeType | None = None
42
55
 
43
56
  # unrestricted
44
- ucode = None
57
+ ucode: types.CodeType | None = None
45
58
 
46
59
  # Names used by the expression
47
- used = None
60
+ used: tuple[str, ...] | None = None
48
61
 
49
- def __init__(self, expr):
62
+ def __init__(self, expr: str):
50
63
  """Create a restricted expression
51
64
 
52
65
  where:
@@ -60,7 +73,7 @@ class RestrictionCapableEval:
60
73
  # Catch syntax errors.
61
74
  self.prepUnrestrictedCode()
62
75
 
63
- def prepRestrictedCode(self):
76
+ def prepRestrictedCode(self) -> None:
64
77
  if self.rcode is None:
65
78
  result = compile_restricted_eval(self.expr, '<string>')
66
79
  if result.errors:
@@ -68,13 +81,12 @@ class RestrictionCapableEval:
68
81
  self.used = tuple(result.used_names)
69
82
  self.rcode = result.code
70
83
 
71
- def prepUnrestrictedCode(self):
84
+ def prepUnrestrictedCode(self) -> None:
72
85
  if self.ucode is None:
73
- exp_node = compile(
86
+ exp_node = ast.parse(
74
87
  self.expr,
75
88
  '<string>',
76
- 'eval',
77
- ast.PyCF_ONLY_AST)
89
+ 'eval')
78
90
 
79
91
  co = compile(exp_node, '<string>', 'eval')
80
92
 
@@ -90,7 +102,9 @@ class RestrictionCapableEval:
90
102
 
91
103
  self.ucode = co
92
104
 
93
- def eval(self, mapping):
105
+ def eval(self,
106
+ mapping: collections.abc.Mapping[str,
107
+ typing.Any]) -> typing.Any:
94
108
  # This default implementation is probably not very useful. :-(
95
109
  # This is meant to be overridden.
96
110
  self.prepRestrictedCode()
@@ -103,11 +117,11 @@ class RestrictionCapableEval:
103
117
 
104
118
  global_scope.update(self.globals)
105
119
 
106
- for name in self.used:
120
+ for name in cast_not_none(self.used):
107
121
  if (name not in global_scope) and (name in mapping):
108
122
  global_scope[name] = mapping[name]
109
123
 
110
- return eval(self.rcode, global_scope)
124
+ return eval(cast_not_none(self.rcode), global_scope)
111
125
 
112
- def __call__(self, **kw):
126
+ def __call__(self, **kw: typing.Any) -> typing.Any:
113
127
  return self.eval(kw)
@@ -16,6 +16,7 @@
16
16
  # DocumentTemplate.DT_UTil contains a few.
17
17
 
18
18
  import builtins
19
+ import string
19
20
 
20
21
  from RestrictedPython.transformer import INSPECT_ATTRIBUTES
21
22
 
@@ -220,7 +221,7 @@ def _full_write_guard():
220
221
  return guard
221
222
 
222
223
 
223
- full_write_guard = _full_write_guard()
224
+ full_write_guard = _full_write_guard() # type: ignore[no-untyped-call]
224
225
 
225
226
 
226
227
  def guarded_setattr(object, name, value):
@@ -238,6 +239,8 @@ safe_builtins['delattr'] = guarded_delattr
238
239
 
239
240
 
240
241
  raise_ = object()
242
+ _FORMATTER_UNSAFE_METHODS = frozenset(('format', 'get_field', 'get_value',
243
+ 'vformat'))
241
244
 
242
245
 
243
246
  def safer_getattr(object, name, default=None, getattr=getattr):
@@ -254,6 +257,14 @@ def safer_getattr(object, name, default=None, getattr=getattr):
254
257
  (isinstance(object, type) and issubclass(object, str))):
255
258
  raise NotImplementedError(
256
259
  'Using the format*() methods of `str` is not safe')
260
+ if object is string and name == 'Formatter':
261
+ raise NotImplementedError('string.Formatter is not safe')
262
+ if name in _FORMATTER_UNSAFE_METHODS and (
263
+ isinstance(object, string.Formatter) or
264
+ (isinstance(object, type) and
265
+ issubclass(object, string.Formatter))):
266
+ raise NotImplementedError(
267
+ 'Using string.Formatter methods is not safe')
257
268
  if name in INSPECT_ATTRIBUTES:
258
269
  raise AttributeError(
259
270
  f'"{name}" is a restricted name,'
@@ -10,11 +10,28 @@
10
10
  # FOR A PARTICULAR PURPOSE
11
11
  #
12
12
  ##############################################################################
13
+ import collections.abc
14
+ import typing
13
15
 
14
- limited_builtins = {}
15
16
 
17
+ limited_builtins: dict[str, typing.Any] = {}
16
18
 
17
- def limited_range(iFirst, *args):
19
+
20
+ @typing.overload
21
+ def limited_range(iFirst: int) -> collections.abc.Sequence[int]: ...
22
+
23
+
24
+ @typing.overload
25
+ def limited_range(iStart: int, iEnd: int, /
26
+ ) -> collections.abc.Sequence[int]: ...
27
+
28
+
29
+ @typing.overload
30
+ def limited_range(iStart: int, iEnd: int, iStep: int, /
31
+ ) -> collections.abc.Sequence[int]: ...
32
+
33
+
34
+ def limited_range(iFirst: int, *args: int) -> collections.abc.Sequence[int]:
18
35
  # limited range function from Martijn Pieters
19
36
  RANGELIMIT = 1000
20
37
  if not len(args):
@@ -41,8 +58,10 @@ def limited_range(iFirst, *args):
41
58
 
42
59
  limited_builtins['range'] = limited_range
43
60
 
61
+ _T = typing.TypeVar('_T')
62
+
44
63
 
45
- def limited_list(seq):
64
+ def limited_list(seq: collections.abc.Iterable[_T]) -> list[_T]:
46
65
  if isinstance(seq, str):
47
66
  raise TypeError('cannot convert string to list')
48
67
  return list(seq)
@@ -51,7 +70,7 @@ def limited_list(seq):
51
70
  limited_builtins['list'] = limited_list
52
71
 
53
72
 
54
- def limited_tuple(seq):
73
+ def limited_tuple(seq: collections.abc.Iterable[_T]) -> tuple[_T, ...]:
55
74
  if isinstance(seq, str):
56
75
  raise TypeError('cannot convert string to tuple')
57
76
  return tuple(seq)
@@ -15,17 +15,17 @@
15
15
  class PrintCollector:
16
16
  """Collect written text, and return it when called."""
17
17
 
18
- def __init__(self, _getattr_=None):
18
+ def __init__(self, _getattr_=None): # type: ignore[no-untyped-def]
19
19
  self.txt = []
20
20
  self._getattr_ = _getattr_
21
21
 
22
- def write(self, text):
22
+ def write(self, text: str) -> None:
23
23
  self.txt.append(text)
24
24
 
25
- def __call__(self):
25
+ def __call__(self) -> str:
26
26
  return ''.join(self.txt)
27
27
 
28
- def _call_print(self, *objects, **kwargs):
28
+ def _call_print(self, *objects, **kwargs): # type: ignore[no-untyped-def]
29
29
  if kwargs.get('file', None) is None:
30
30
  kwargs['file'] = self
31
31
  else:
@@ -11,21 +11,24 @@
11
11
  #
12
12
  ##############################################################################
13
13
 
14
+ import collections.abc
14
15
  import math
15
16
  import random
16
17
  import string
18
+ import types
19
+ import typing
17
20
 
18
21
 
19
- utility_builtins = {}
22
+ utility_builtins: dict[str, typing.Any] = {}
20
23
 
21
24
 
22
25
  class _AttributeDelegator:
23
- def __init__(self, mod, *excludes):
26
+ def __init__(self, mod: types.ModuleType, *excludes: str):
24
27
  """delegate attribute lookups outside *excludes* to module *mod*."""
25
28
  self.__mod = mod
26
29
  self.__excludes = excludes
27
30
 
28
- def __getattr__(self, attr):
31
+ def __getattr__(self, attr: str) -> typing.Any:
29
32
  if attr in self.__excludes:
30
33
  raise NotImplementedError(
31
34
  f"{self.__mod.__name__}.{attr} is not safe")
@@ -50,7 +53,7 @@ except ImportError:
50
53
  pass
51
54
 
52
55
 
53
- def same_type(arg1, *args):
56
+ def same_type(arg1: object, *args: object) -> bool:
54
57
  """Compares the class or type of two or more objects."""
55
58
  t = getattr(arg1, '__class__', type(arg1))
56
59
  for arg in args:
@@ -61,8 +64,10 @@ def same_type(arg1, *args):
61
64
 
62
65
  utility_builtins['same_type'] = same_type
63
66
 
67
+ _T = typing.TypeVar('_T')
64
68
 
65
- def test(*args):
69
+
70
+ def test(*args: _T) -> _T | None:
66
71
  length = len(args)
67
72
  for i in range(1, length, 2):
68
73
  if args[i - 1]:
@@ -70,12 +75,22 @@ def test(*args):
70
75
 
71
76
  if length % 2:
72
77
  return args[-1]
78
+ return None
73
79
 
74
80
 
75
81
  utility_builtins['test'] = test
76
82
 
83
+ _TK = typing.TypeVar('_TK')
84
+ _TV = typing.TypeVar('_TV')
85
+ _T_in: typing.TypeAlias = collections.abc.Iterable[_TK | tuple[_TK, _TV]]
86
+ _T_out: typing.TypeAlias = list[tuple[_TK, _TK | _TV]]
87
+
77
88
 
78
- def reorder(s, with_=None, without=()):
89
+ def reorder(
90
+ s: _T_in[_TK, _TV],
91
+ with_: collections.abc.Iterable[typing.Any] | None = None,
92
+ without: collections.abc.Iterable[typing.Any] = ()
93
+ ) -> _T_out[_TK, _TV]:
79
94
  # s, with_, and without are sequences treated as sets.
80
95
  # The result is subtract(intersect(s, with_), without),
81
96
  # unless with_ is None, in which case it is subtract(s, without).
@@ -0,0 +1,25 @@
1
+ import ast
2
+ import sys
3
+ import typing
4
+
5
+
6
+ _T = typing.TypeVar('_T')
7
+
8
+
9
+ def cast_not_none(var: _T | None) -> _T:
10
+ return typing.cast(_T, var)
11
+
12
+
13
+ # T_pos_ast are subtypes of ast.AST that have a position
14
+ # (have attributes: lineno, end_lineno, col_offset, and end_col_offset).
15
+ #
16
+ # ast.type_param is a new type in python 3.12 that has a position.
17
+ # TODO: Remove `else` when Support for Python 3.11 is dropped.
18
+ if sys.version_info >= (3, 12):
19
+ T_pos_ast: typing.TypeAlias = (
20
+ ast.stmt | ast.expr | ast.excepthandler | ast.arg | ast.keyword
21
+ | ast.alias | ast.pattern | ast.type_param)
22
+ else:
23
+ T_pos_ast: typing.TypeAlias = (
24
+ ast.stmt | ast.expr | ast.excepthandler | ast.arg | ast.keyword
25
+ | ast.alias | ast.pattern)