fishertools 0.2.1__py3-none-any.whl → 0.4.0__py3-none-any.whl
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.
- fishertools/__init__.py +16 -5
- fishertools/errors/__init__.py +11 -3
- fishertools/errors/exception_types.py +282 -0
- fishertools/errors/explainer.py +87 -1
- fishertools/errors/models.py +73 -1
- fishertools/errors/patterns.py +40 -0
- fishertools/examples/cli_example.py +156 -0
- fishertools/examples/learn_example.py +65 -0
- fishertools/examples/logger_example.py +176 -0
- fishertools/examples/menu_example.py +101 -0
- fishertools/examples/storage_example.py +175 -0
- fishertools/input_utils.py +185 -0
- fishertools/learn/__init__.py +19 -2
- fishertools/learn/examples.py +88 -1
- fishertools/learn/knowledge_engine.py +321 -0
- fishertools/learn/repl/__init__.py +19 -0
- fishertools/learn/repl/cli.py +31 -0
- fishertools/learn/repl/code_sandbox.py +229 -0
- fishertools/learn/repl/command_handler.py +544 -0
- fishertools/learn/repl/command_parser.py +165 -0
- fishertools/learn/repl/engine.py +479 -0
- fishertools/learn/repl/models.py +121 -0
- fishertools/learn/repl/session_manager.py +284 -0
- fishertools/learn/repl/test_code_sandbox.py +261 -0
- fishertools/learn/repl/test_code_sandbox_pbt.py +148 -0
- fishertools/learn/repl/test_command_handler.py +224 -0
- fishertools/learn/repl/test_command_handler_pbt.py +189 -0
- fishertools/learn/repl/test_command_parser.py +160 -0
- fishertools/learn/repl/test_command_parser_pbt.py +100 -0
- fishertools/learn/repl/test_engine.py +190 -0
- fishertools/learn/repl/test_session_manager.py +310 -0
- fishertools/learn/repl/test_session_manager_pbt.py +182 -0
- fishertools/learn/test_knowledge_engine.py +241 -0
- fishertools/learn/test_knowledge_engine_pbt.py +180 -0
- fishertools/patterns/__init__.py +46 -0
- fishertools/patterns/cli.py +175 -0
- fishertools/patterns/logger.py +140 -0
- fishertools/patterns/menu.py +99 -0
- fishertools/patterns/storage.py +127 -0
- fishertools/readme_transformer.py +631 -0
- fishertools/safe/__init__.py +6 -1
- fishertools/safe/files.py +329 -1
- fishertools/transform_readme.py +105 -0
- fishertools-0.4.0.dist-info/METADATA +104 -0
- fishertools-0.4.0.dist-info/RECORD +131 -0
- {fishertools-0.2.1.dist-info → fishertools-0.4.0.dist-info}/WHEEL +1 -1
- tests/test_documentation_properties.py +329 -0
- tests/test_documentation_structure.py +349 -0
- tests/test_errors/test_exception_types.py +446 -0
- tests/test_errors/test_exception_types_pbt.py +333 -0
- tests/test_errors/test_patterns.py +52 -0
- tests/test_input_utils/__init__.py +1 -0
- tests/test_input_utils/test_input_utils.py +65 -0
- tests/test_learn/test_examples.py +179 -1
- tests/test_learn/test_explain_properties.py +307 -0
- tests/test_patterns_cli.py +611 -0
- tests/test_patterns_docstrings.py +473 -0
- tests/test_patterns_logger.py +465 -0
- tests/test_patterns_menu.py +440 -0
- tests/test_patterns_storage.py +447 -0
- tests/test_readme_enhancements_v0_3_1.py +2036 -0
- tests/test_readme_transformer/__init__.py +1 -0
- tests/test_readme_transformer/test_readme_infrastructure.py +1023 -0
- tests/test_readme_transformer/test_transform_readme_integration.py +431 -0
- tests/test_safe/test_files.py +726 -1
- fishertools-0.2.1.dist-info/METADATA +0 -256
- fishertools-0.2.1.dist-info/RECORD +0 -81
- {fishertools-0.2.1.dist-info → fishertools-0.4.0.dist-info}/licenses/LICENSE +0 -0
- {fishertools-0.2.1.dist-info → fishertools-0.4.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Code sandbox for safely executing user code in the REPL.
|
|
3
|
+
|
|
4
|
+
This module provides a restricted execution environment that prevents access to
|
|
5
|
+
dangerous operations like file I/O, imports, and other restricted functions.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import sys
|
|
9
|
+
import io
|
|
10
|
+
import signal
|
|
11
|
+
from contextlib import redirect_stdout, redirect_stderr
|
|
12
|
+
from typing import Tuple, Dict, Any, Set
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class CodeSandbox:
|
|
16
|
+
"""
|
|
17
|
+
Safely executes Python code with restrictions and timeout support.
|
|
18
|
+
|
|
19
|
+
The sandbox prevents access to:
|
|
20
|
+
- File I/O operations (open, read, write)
|
|
21
|
+
- Module imports
|
|
22
|
+
- System operations (os, sys, subprocess)
|
|
23
|
+
- Dangerous built-in functions
|
|
24
|
+
|
|
25
|
+
Example:
|
|
26
|
+
>>> sandbox = CodeSandbox()
|
|
27
|
+
>>> success, output = sandbox.execute("print(2 + 2)")
|
|
28
|
+
>>> print(output)
|
|
29
|
+
4
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
# Built-in functions that are allowed in the sandbox
|
|
33
|
+
ALLOWED_BUILTINS = {
|
|
34
|
+
"abs", "all", "any", "ascii", "bin", "bool", "bytearray", "bytes",
|
|
35
|
+
"chr", "complex", "dict", "dir", "divmod", "enumerate", "filter",
|
|
36
|
+
"float", "format", "frozenset", "getattr", "hasattr", "hash", "hex",
|
|
37
|
+
"id", "input", "int", "isinstance", "issubclass", "iter", "len",
|
|
38
|
+
"list", "locals", "map", "max", "min", "next", "object", "oct",
|
|
39
|
+
"ord", "pow", "print", "property", "range", "repr", "reversed",
|
|
40
|
+
"round", "set", "setattr", "slice", "sorted", "staticmethod", "str",
|
|
41
|
+
"sum", "super", "tuple", "type", "vars", "zip",
|
|
42
|
+
# Math functions
|
|
43
|
+
"abs", "divmod", "pow", "round",
|
|
44
|
+
# Type constructors
|
|
45
|
+
"bool", "int", "float", "complex", "str", "bytes", "bytearray",
|
|
46
|
+
"list", "tuple", "dict", "set", "frozenset",
|
|
47
|
+
# Iteration
|
|
48
|
+
"enumerate", "filter", "map", "reversed", "sorted", "zip",
|
|
49
|
+
# Other safe functions
|
|
50
|
+
"len", "min", "max", "sum", "all", "any",
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
# Dangerous built-in functions that should be blocked
|
|
54
|
+
BLOCKED_BUILTINS = {
|
|
55
|
+
"open", "input", "exec", "eval", "compile", "__import__",
|
|
56
|
+
"globals", "locals", "vars", "dir", "getattr", "setattr",
|
|
57
|
+
"delattr", "hasattr", "callable", "classmethod", "staticmethod",
|
|
58
|
+
"property", "super", "type", "object", "memoryview",
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
# Dangerous modules that cannot be imported
|
|
62
|
+
BLOCKED_MODULES = {
|
|
63
|
+
"os", "sys", "subprocess", "socket", "urllib", "requests",
|
|
64
|
+
"pickle", "shelve", "dbm", "sqlite3", "tempfile", "shutil",
|
|
65
|
+
"glob", "fnmatch", "linecache", "fileinput", "stat", "filecmp",
|
|
66
|
+
"pathlib", "zipfile", "tarfile", "gzip", "bz2", "lzma",
|
|
67
|
+
"zlib", "configparser", "netrc", "xdrlib", "plistlib",
|
|
68
|
+
"hashlib", "hmac", "secrets", "ssl", "asyncio", "threading",
|
|
69
|
+
"multiprocessing", "concurrent", "ctypes", "mmap", "select",
|
|
70
|
+
"selectors", "fcntl", "resource", "nis", "syslog", "grp", "pwd",
|
|
71
|
+
"spwd", "crypt", "termios", "tty", "pty", "fcntl", "pipes",
|
|
72
|
+
"posixfile", "resource", "nis", "syslog", "grp", "pwd",
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
def __init__(self, timeout: float = 5.0):
|
|
76
|
+
"""
|
|
77
|
+
Initialize the code sandbox.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
timeout: Maximum execution time in seconds (default: 5.0)
|
|
81
|
+
"""
|
|
82
|
+
self.timeout = timeout
|
|
83
|
+
self.execution_count = 0
|
|
84
|
+
|
|
85
|
+
def execute(self, code: str, timeout: float = None) -> Tuple[bool, str]:
|
|
86
|
+
"""
|
|
87
|
+
Execute code safely in a sandbox.
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
code: Python code to execute
|
|
91
|
+
timeout: Optional timeout override (in seconds)
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
Tuple of (success, output_or_error) where:
|
|
95
|
+
- success: True if code executed without errors
|
|
96
|
+
- output_or_error: Captured output or error message
|
|
97
|
+
|
|
98
|
+
Example:
|
|
99
|
+
>>> sandbox = CodeSandbox()
|
|
100
|
+
>>> success, output = sandbox.execute("print('Hello')")
|
|
101
|
+
>>> print(success, output)
|
|
102
|
+
True Hello
|
|
103
|
+
"""
|
|
104
|
+
if timeout is None:
|
|
105
|
+
timeout = self.timeout
|
|
106
|
+
|
|
107
|
+
# Validate code before execution
|
|
108
|
+
validation_error = self._validate_code(code)
|
|
109
|
+
if validation_error:
|
|
110
|
+
return False, validation_error
|
|
111
|
+
|
|
112
|
+
# Create restricted globals
|
|
113
|
+
restricted_globals = self._create_restricted_globals()
|
|
114
|
+
|
|
115
|
+
# Capture output
|
|
116
|
+
output_buffer = io.StringIO()
|
|
117
|
+
error_buffer = io.StringIO()
|
|
118
|
+
|
|
119
|
+
try:
|
|
120
|
+
# Execute code with output redirection
|
|
121
|
+
with redirect_stdout(output_buffer), redirect_stderr(error_buffer):
|
|
122
|
+
exec(code, restricted_globals)
|
|
123
|
+
|
|
124
|
+
output = output_buffer.getvalue()
|
|
125
|
+
return True, output
|
|
126
|
+
|
|
127
|
+
except SyntaxError as e:
|
|
128
|
+
error_msg = f"Syntax Error: {e.msg}"
|
|
129
|
+
if e.lineno:
|
|
130
|
+
error_msg += f" (line {e.lineno})"
|
|
131
|
+
return False, error_msg
|
|
132
|
+
|
|
133
|
+
except TimeoutError:
|
|
134
|
+
return False, "Code execution timed out. Try simpler code."
|
|
135
|
+
|
|
136
|
+
except Exception as e:
|
|
137
|
+
error_type = type(e).__name__
|
|
138
|
+
error_msg = str(e)
|
|
139
|
+
return False, f"{error_type}: {error_msg}"
|
|
140
|
+
|
|
141
|
+
def _validate_code(self, code: str) -> str:
|
|
142
|
+
"""
|
|
143
|
+
Validate code for dangerous operations.
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
code: Code to validate
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
Error message if validation fails, empty string if valid
|
|
150
|
+
"""
|
|
151
|
+
if not code or not code.strip():
|
|
152
|
+
return "Code cannot be empty"
|
|
153
|
+
|
|
154
|
+
# Check for dangerous imports
|
|
155
|
+
dangerous_keywords = ["import ", "from ", "__import__"]
|
|
156
|
+
code_lower = code.lower()
|
|
157
|
+
|
|
158
|
+
for keyword in dangerous_keywords:
|
|
159
|
+
if keyword in code_lower:
|
|
160
|
+
return "Imports are not allowed in the sandbox"
|
|
161
|
+
|
|
162
|
+
# Check for file operations
|
|
163
|
+
file_keywords = ["open(", "read(", "write(", "file("]
|
|
164
|
+
for keyword in file_keywords:
|
|
165
|
+
if keyword in code_lower:
|
|
166
|
+
return "File operations are not allowed in the sandbox"
|
|
167
|
+
|
|
168
|
+
# Check for dangerous functions
|
|
169
|
+
dangerous_funcs = ["exec(", "eval(", "compile(", "globals(", "locals("]
|
|
170
|
+
for func in dangerous_funcs:
|
|
171
|
+
if func in code_lower:
|
|
172
|
+
return f"The function '{func[:-1]}' is not allowed in the sandbox"
|
|
173
|
+
|
|
174
|
+
return ""
|
|
175
|
+
|
|
176
|
+
def _create_restricted_globals(self) -> Dict[str, Any]:
|
|
177
|
+
"""
|
|
178
|
+
Create a restricted globals dictionary for code execution.
|
|
179
|
+
|
|
180
|
+
Returns:
|
|
181
|
+
Dictionary with safe built-in functions and common modules
|
|
182
|
+
"""
|
|
183
|
+
# Start with safe built-ins
|
|
184
|
+
safe_builtins = {
|
|
185
|
+
name: __builtins__[name]
|
|
186
|
+
for name in self.ALLOWED_BUILTINS
|
|
187
|
+
if name in __builtins__
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
# Add safe modules
|
|
191
|
+
import math
|
|
192
|
+
safe_modules = {
|
|
193
|
+
"math": math,
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
# Combine into globals
|
|
197
|
+
restricted_globals = {
|
|
198
|
+
"__builtins__": safe_builtins,
|
|
199
|
+
**safe_modules,
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return restricted_globals
|
|
203
|
+
|
|
204
|
+
def get_available_builtins(self) -> list:
|
|
205
|
+
"""
|
|
206
|
+
Get list of available built-in functions in the sandbox.
|
|
207
|
+
|
|
208
|
+
Returns:
|
|
209
|
+
Sorted list of available built-in function names
|
|
210
|
+
"""
|
|
211
|
+
return sorted(self.ALLOWED_BUILTINS)
|
|
212
|
+
|
|
213
|
+
def get_blocked_builtins(self) -> list:
|
|
214
|
+
"""
|
|
215
|
+
Get list of blocked built-in functions.
|
|
216
|
+
|
|
217
|
+
Returns:
|
|
218
|
+
Sorted list of blocked built-in function names
|
|
219
|
+
"""
|
|
220
|
+
return sorted(self.BLOCKED_BUILTINS)
|
|
221
|
+
|
|
222
|
+
def get_blocked_modules(self) -> list:
|
|
223
|
+
"""
|
|
224
|
+
Get list of blocked modules.
|
|
225
|
+
|
|
226
|
+
Returns:
|
|
227
|
+
Sorted list of blocked module names
|
|
228
|
+
"""
|
|
229
|
+
return sorted(self.BLOCKED_MODULES)
|