webexec 1.0.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.
- webexec/__init__.py +28 -0
- webexec/cache.py +42 -0
- webexec/executor.py +94 -0
- webexec/fetcher.py +52 -0
- webexec/utils.py +132 -0
- webexec-1.0.0.dist-info/METADATA +220 -0
- webexec-1.0.0.dist-info/RECORD +10 -0
- webexec-1.0.0.dist-info/WHEEL +5 -0
- webexec-1.0.0.dist-info/licenses/LICENSE +21 -0
- webexec-1.0.0.dist-info/top_level.txt +1 -0
webexec/__init__.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""
|
|
2
|
+
webexec - Fetch and execute Python scripts from any raw URL.
|
|
3
|
+
|
|
4
|
+
Created by ZeroTeam.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from .executor import run, load, run_all, watch
|
|
8
|
+
from .utils import preview, install_imports
|
|
9
|
+
from .fetcher import fetch_content
|
|
10
|
+
from .cache import clear_cache
|
|
11
|
+
|
|
12
|
+
# Export all public functions
|
|
13
|
+
__all__ = [
|
|
14
|
+
'run',
|
|
15
|
+
'load',
|
|
16
|
+
'fetch',
|
|
17
|
+
'clear_cache',
|
|
18
|
+
'watch',
|
|
19
|
+
'run_all',
|
|
20
|
+
'preview',
|
|
21
|
+
'install_imports'
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
# Alias fetch_content to fetch for the public API
|
|
25
|
+
fetch = fetch_content
|
|
26
|
+
|
|
27
|
+
__version__ = "1.0.0"
|
|
28
|
+
__author__ = "ZeroTeam"
|
webexec/cache.py
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""
|
|
2
|
+
In-memory cache for webexec package.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import time
|
|
6
|
+
from typing import Dict, Optional
|
|
7
|
+
|
|
8
|
+
class Cache:
|
|
9
|
+
"""Simple in-memory cache with timestamp support."""
|
|
10
|
+
|
|
11
|
+
def __init__(self):
|
|
12
|
+
self._cache: Dict[str, tuple] = {}
|
|
13
|
+
|
|
14
|
+
def get(self, key: str) -> Optional[str]:
|
|
15
|
+
"""Get cached content if available."""
|
|
16
|
+
if key in self._cache:
|
|
17
|
+
content, timestamp = self._cache[key]
|
|
18
|
+
return content
|
|
19
|
+
return None
|
|
20
|
+
|
|
21
|
+
def set(self, key: str, value: str) -> None:
|
|
22
|
+
"""Cache content with current timestamp."""
|
|
23
|
+
self._cache[key] = (value, time.time())
|
|
24
|
+
|
|
25
|
+
def clear(self) -> None:
|
|
26
|
+
"""Clear all cached content."""
|
|
27
|
+
self._cache.clear()
|
|
28
|
+
|
|
29
|
+
def size(self) -> int:
|
|
30
|
+
"""Return number of cached items."""
|
|
31
|
+
return len(self._cache)
|
|
32
|
+
|
|
33
|
+
# Global cache instance
|
|
34
|
+
_cache = Cache()
|
|
35
|
+
|
|
36
|
+
def get_cache() -> Cache:
|
|
37
|
+
"""Get the global cache instance."""
|
|
38
|
+
return _cache
|
|
39
|
+
|
|
40
|
+
def clear_cache() -> None:
|
|
41
|
+
"""Clear the global cache."""
|
|
42
|
+
_cache.clear()
|
webexec/executor.py
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Execution functionality for webexec package.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import time
|
|
6
|
+
import threading
|
|
7
|
+
import types
|
|
8
|
+
import importlib.util
|
|
9
|
+
from typing import List, Optional, Dict, Any
|
|
10
|
+
from .fetcher import fetch_content
|
|
11
|
+
|
|
12
|
+
def run(url: str, *, timeout: int = 10, cache: bool = False, globals_: Optional[Dict[str, Any]] = None) -> None:
|
|
13
|
+
"""
|
|
14
|
+
Fetch and execute a Python script from a URL.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
url: The URL to fetch the script from
|
|
18
|
+
timeout: Request timeout in seconds
|
|
19
|
+
cache: Whether to use cached content if available
|
|
20
|
+
globals_: Global variables to use during execution
|
|
21
|
+
"""
|
|
22
|
+
content = fetch_content(url, timeout=timeout, cache=cache)
|
|
23
|
+
|
|
24
|
+
if globals_ is None:
|
|
25
|
+
globals_ = {}
|
|
26
|
+
|
|
27
|
+
exec(content, globals_)
|
|
28
|
+
|
|
29
|
+
def load(url: str, module_name: Optional[str] = None, *, timeout: int = 10, cache: bool = False) -> types.ModuleType:
|
|
30
|
+
"""
|
|
31
|
+
Fetch a script from a URL and return it as an importable module.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
url: The URL to fetch the script from
|
|
35
|
+
module_name: Name for the created module (auto-generated if None)
|
|
36
|
+
timeout: Request timeout in seconds
|
|
37
|
+
cache: Whether to use cached content if available
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
A module object containing the fetched script
|
|
41
|
+
"""
|
|
42
|
+
content = fetch_content(url, timeout=timeout, cache=cache)
|
|
43
|
+
|
|
44
|
+
if module_name is None:
|
|
45
|
+
module_name = f"webexec_module_{hash(url) % 1000000}"
|
|
46
|
+
|
|
47
|
+
spec = importlib.util.spec_from_loader(module_name, loader=None)
|
|
48
|
+
module = importlib.util.module_from_spec(spec)
|
|
49
|
+
|
|
50
|
+
exec(content, module.__dict__)
|
|
51
|
+
|
|
52
|
+
return module
|
|
53
|
+
|
|
54
|
+
def run_all(urls: List[str], *, timeout: int = 10, cache: bool = False, globals_: Optional[Dict[str, Any]] = None) -> None:
|
|
55
|
+
"""
|
|
56
|
+
Fetch and run multiple scripts in order.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
urls: List of URLs to fetch and execute
|
|
60
|
+
timeout: Request timeout in seconds
|
|
61
|
+
cache: Whether to use cached content if available
|
|
62
|
+
globals_: Global variables to use during execution
|
|
63
|
+
"""
|
|
64
|
+
if globals_ is None:
|
|
65
|
+
globals_ = {}
|
|
66
|
+
|
|
67
|
+
for url in urls:
|
|
68
|
+
run(url, timeout=timeout, cache=cache, globals_=globals_)
|
|
69
|
+
|
|
70
|
+
def watch(url: str, interval: int = 60, *, timeout: int = 10, cache: bool = False, globals_: Optional[Dict[str, Any]] = None) -> threading.Thread:
|
|
71
|
+
"""
|
|
72
|
+
Re-fetch and re-run a script every X seconds in a background thread.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
url: The URL to watch
|
|
76
|
+
interval: Interval in seconds between re-runs
|
|
77
|
+
timeout: Request timeout in seconds
|
|
78
|
+
cache: Whether to use cached content if available
|
|
79
|
+
globals_: Global variables to use during execution
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
The background thread object
|
|
83
|
+
"""
|
|
84
|
+
def watch_loop():
|
|
85
|
+
while True:
|
|
86
|
+
try:
|
|
87
|
+
run(url, timeout=timeout, cache=False, globals_=globals_) # Don't use cache for watching
|
|
88
|
+
except Exception as e:
|
|
89
|
+
print(f"Error watching {url}: {e}")
|
|
90
|
+
time.sleep(interval)
|
|
91
|
+
|
|
92
|
+
thread = threading.Thread(target=watch_loop, daemon=True)
|
|
93
|
+
thread.start()
|
|
94
|
+
return thread
|
webexec/fetcher.py
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""
|
|
2
|
+
HTTP fetching functionality for webexec package.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import urllib.request
|
|
6
|
+
import urllib.error
|
|
7
|
+
from typing import Optional
|
|
8
|
+
from .cache import get_cache
|
|
9
|
+
|
|
10
|
+
def fetch_content(url: str, timeout: int = 10, cache: bool = False) -> str:
|
|
11
|
+
"""
|
|
12
|
+
Fetch content from a URL.
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
url: The URL to fetch from
|
|
16
|
+
timeout: Request timeout in seconds
|
|
17
|
+
cache: Whether to use cached content if available
|
|
18
|
+
|
|
19
|
+
Returns:
|
|
20
|
+
The fetched content as string
|
|
21
|
+
|
|
22
|
+
Raises:
|
|
23
|
+
RuntimeError: If the request fails
|
|
24
|
+
"""
|
|
25
|
+
if cache:
|
|
26
|
+
cached_content = get_cache().get(url)
|
|
27
|
+
if cached_content is not None:
|
|
28
|
+
return cached_content
|
|
29
|
+
|
|
30
|
+
try:
|
|
31
|
+
req = urllib.request.Request(url)
|
|
32
|
+
req.add_header('User-Agent', 'webexec/1.0')
|
|
33
|
+
|
|
34
|
+
with urllib.request.urlopen(req, timeout=timeout) as response:
|
|
35
|
+
if response.status != 200:
|
|
36
|
+
raise RuntimeError(f"HTTP {response.status}: Failed to fetch {url}")
|
|
37
|
+
|
|
38
|
+
content = response.read().decode('utf-8')
|
|
39
|
+
|
|
40
|
+
if cache:
|
|
41
|
+
get_cache().set(url, content)
|
|
42
|
+
|
|
43
|
+
return content
|
|
44
|
+
|
|
45
|
+
except urllib.error.HTTPError as e:
|
|
46
|
+
raise RuntimeError(f"HTTP {e.code}: Failed to fetch {url}")
|
|
47
|
+
except urllib.error.URLError as e:
|
|
48
|
+
raise RuntimeError(f"Network error: Failed to fetch {url} - {e.reason}")
|
|
49
|
+
except UnicodeDecodeError:
|
|
50
|
+
raise RuntimeError(f"Invalid content encoding: {url} does not contain valid UTF-8 text")
|
|
51
|
+
except Exception as e:
|
|
52
|
+
raise RuntimeError(f"Unexpected error fetching {url}: {e}")
|
webexec/utils.py
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Utility functions for webexec package.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import ast
|
|
6
|
+
import os
|
|
7
|
+
import subprocess
|
|
8
|
+
import sys
|
|
9
|
+
from typing import Optional, List, Set
|
|
10
|
+
from .fetcher import fetch_content
|
|
11
|
+
|
|
12
|
+
def preview(url: str, *, timeout: int = 10, cache: bool = False) -> None:
|
|
13
|
+
"""
|
|
14
|
+
Print the source code from a URL without executing it.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
url: The URL to preview
|
|
18
|
+
timeout: Request timeout in seconds
|
|
19
|
+
cache: Whether to use cached content if available
|
|
20
|
+
"""
|
|
21
|
+
content = fetch_content(url, timeout=timeout, cache=cache)
|
|
22
|
+
print(content)
|
|
23
|
+
|
|
24
|
+
def install_imports(url: str, *, timeout: int = 10, cache: bool = False, upgrade: bool = False, user: bool = False) -> List[str]:
|
|
25
|
+
"""
|
|
26
|
+
Detect and install missing imports from a remote script.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
url: The URL to analyze and install imports for
|
|
30
|
+
timeout: Request timeout in seconds
|
|
31
|
+
cache: Whether to use cached content if available
|
|
32
|
+
upgrade: Whether to upgrade existing packages
|
|
33
|
+
user: Whether to install in user site-packages
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
List of packages that were attempted to install
|
|
37
|
+
|
|
38
|
+
Raises:
|
|
39
|
+
RuntimeError: If URL fetch fails or pip installation encounters errors
|
|
40
|
+
"""
|
|
41
|
+
content = fetch_content(url, timeout=timeout, cache=cache)
|
|
42
|
+
|
|
43
|
+
# Parse the AST to find all import statements
|
|
44
|
+
try:
|
|
45
|
+
tree = ast.parse(content)
|
|
46
|
+
except SyntaxError as e:
|
|
47
|
+
raise RuntimeError(f"Invalid Python syntax in {url}: {e}")
|
|
48
|
+
|
|
49
|
+
# Extract imported module names
|
|
50
|
+
imported_modules = set()
|
|
51
|
+
|
|
52
|
+
for node in ast.walk(tree):
|
|
53
|
+
if isinstance(node, ast.Import):
|
|
54
|
+
for alias in node.names:
|
|
55
|
+
# Get the top-level package name
|
|
56
|
+
module_name = alias.name.split('.')[0]
|
|
57
|
+
imported_modules.add(module_name)
|
|
58
|
+
elif isinstance(node, ast.ImportFrom):
|
|
59
|
+
if node.module:
|
|
60
|
+
# Get the top-level package name
|
|
61
|
+
module_name = node.module.split('.')[0]
|
|
62
|
+
imported_modules.add(module_name)
|
|
63
|
+
|
|
64
|
+
# Filter out standard library modules
|
|
65
|
+
stdlib_modules = {
|
|
66
|
+
'argparse', 'array', 'asyncio', 'base64', 'binascii', 'bisect', 'builtins',
|
|
67
|
+
'calendar', 'cgi', 'cgitb', 'codecs', 'collections', 'colorsys', 'compileall',
|
|
68
|
+
'concurrent', 'configparser', 'contextlib', 'copy', 'copyreg', 'csv',
|
|
69
|
+
'ctypes', 'datetime', 'decimal', 'difflib', 'dis', 'doctest', 'email',
|
|
70
|
+
'enum', 'errno', 'faulthandler', 'fcntl', 'filecmp', 'fileinput', 'fnmatch',
|
|
71
|
+
'fractions', 'ftplib', 'functools', 'gc', 'getopt', 'getpass', 'gettext',
|
|
72
|
+
'glob', 'grp', 'gzip', 'hashlib', 'heapq', 'hmac', 'html', 'http',
|
|
73
|
+
'imaplib', 'imghdr', 'imp', 'importlib', 'inspect', 'io', 'ipaddress',
|
|
74
|
+
'itertools', 'json', 'keyword', 'linecache', 'locale', 'logging',
|
|
75
|
+
'lzma', 'mailbox', 'mailcap', 'marshal', 'math', 'mimetypes', 'mmap',
|
|
76
|
+
'modulefinder', 'multiprocessing', 'netrc', 'nntplib', 'numbers',
|
|
77
|
+
'operator', 'os', 'ossaudiodev', 'pathlib', 'pdb', 'pickle', 'pickletools',
|
|
78
|
+
'pipes', 'pkgutil', 'platform', 'plistlib', 'poplib', 'posix', 'pprint',
|
|
79
|
+
'profile', 'pstats', 'pty', 'pwd', 'py_compile', 'pyclbr', 'pydoc',
|
|
80
|
+
'queue', 'quopri', 'random', 're', 'readline', 'reprlib', 'resource',
|
|
81
|
+
'rlcompleter', 'runpy', 'sched', 'secrets', 'select', 'selectors', 'shelve',
|
|
82
|
+
'shlex', 'shutil', 'signal', 'site', 'smtpd', 'smtplib', 'sndhdr', 'socket',
|
|
83
|
+
'socketserver', 'sqlite3', 'ssl', 'stat', 'statistics', 'string',
|
|
84
|
+
'stringprep', 'struct', 'subprocess', 'sunau', 'symbol', 'symtable',
|
|
85
|
+
'sys', 'sysconfig', 'syslog', 'tabnanny', 'tarfile', 'telnetlib', 'tempfile',
|
|
86
|
+
'termios', 'textwrap', 'threading', 'time', 'timeit', 'tkinter', 'token',
|
|
87
|
+
'tokenize', 'trace', 'traceback', 'tracemalloc', 'tty', 'turtle', 'types',
|
|
88
|
+
'typing', 'unicodedata', 'unittest', 'urllib', 'uu', 'uuid', 'venv',
|
|
89
|
+
'warnings', 'wave', 'weakref', 'webbrowser', 'winreg', 'winsound',
|
|
90
|
+
'wsgiref', 'xdrlib', 'xml', 'xmlrpc', 'zipapp', 'zipfile', 'zipimport',
|
|
91
|
+
'zlib'
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
# Filter out standard library and local modules
|
|
95
|
+
third_party_modules = []
|
|
96
|
+
for module in imported_modules:
|
|
97
|
+
if module not in stdlib_modules and not module.startswith('_'):
|
|
98
|
+
# Check if module is already installed
|
|
99
|
+
try:
|
|
100
|
+
__import__(module)
|
|
101
|
+
if upgrade:
|
|
102
|
+
third_party_modules.append(module)
|
|
103
|
+
except ImportError:
|
|
104
|
+
third_party_modules.append(module)
|
|
105
|
+
|
|
106
|
+
# Install missing packages
|
|
107
|
+
installed_packages = []
|
|
108
|
+
|
|
109
|
+
if third_party_modules:
|
|
110
|
+
pip_cmd = [sys.executable, '-m', 'pip', 'install']
|
|
111
|
+
|
|
112
|
+
if upgrade:
|
|
113
|
+
pip_cmd.append('--upgrade')
|
|
114
|
+
if user:
|
|
115
|
+
pip_cmd.append('--user')
|
|
116
|
+
|
|
117
|
+
for package in third_party_modules:
|
|
118
|
+
try:
|
|
119
|
+
install_cmd = pip_cmd + [package]
|
|
120
|
+
result = subprocess.run(
|
|
121
|
+
install_cmd,
|
|
122
|
+
capture_output=True,
|
|
123
|
+
text=True,
|
|
124
|
+
check=True
|
|
125
|
+
)
|
|
126
|
+
installed_packages.append(package)
|
|
127
|
+
print(f"✓ Installed {package}")
|
|
128
|
+
except subprocess.CalledProcessError as e:
|
|
129
|
+
print(f"✗ Failed to install {package}: {e.stderr}")
|
|
130
|
+
raise RuntimeError(f"Failed to install {package}: {e.stderr}")
|
|
131
|
+
|
|
132
|
+
return installed_packages
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: webexec
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Fetch and execute Python scripts from any raw URL
|
|
5
|
+
Author-email: ZeroTeam <zeroteam@example.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/zeroteam/webexec
|
|
8
|
+
Project-URL: Repository, https://github.com/zeroteam/webexec
|
|
9
|
+
Project-URL: Issues, https://github.com/zeroteam/webexec/issues
|
|
10
|
+
Keywords: web,exec,remote,script,fetch,url
|
|
11
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.7
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
23
|
+
Classifier: Topic :: Internet :: WWW/HTTP
|
|
24
|
+
Requires-Python: >=3.7
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
License-File: LICENSE
|
|
27
|
+
Dynamic: license-file
|
|
28
|
+
|
|
29
|
+
# webexec
|
|
30
|
+
|
|
31
|
+
Fetch and execute Python scripts from any raw URL.
|
|
32
|
+
|
|
33
|
+
Created by ZeroTeam.
|
|
34
|
+
|
|
35
|
+
## Installation
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
pip install webexec
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Core Functions
|
|
42
|
+
|
|
43
|
+
### `webexec.run(url, *, timeout=10, cache=False, globals_=None)`
|
|
44
|
+
|
|
45
|
+
Fetch and execute a Python script from a URL.
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
import webexec
|
|
49
|
+
|
|
50
|
+
# Execute a script from Pastebin
|
|
51
|
+
webexec.run("https://pastebin.com/raw/abc123")
|
|
52
|
+
|
|
53
|
+
# Execute with custom globals
|
|
54
|
+
my_globals = {"name": "Alice"}
|
|
55
|
+
webexec.run("https://example.com/script.py", globals_=my_globals)
|
|
56
|
+
|
|
57
|
+
# Use caching
|
|
58
|
+
webexec.run("https://example.com/script.py", cache=True)
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### `webexec.load(url, module_name=None, *, timeout=10, cache=False)`
|
|
62
|
+
|
|
63
|
+
Fetch a script and return it as an importable module.
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
import webexec
|
|
67
|
+
|
|
68
|
+
# Load as module
|
|
69
|
+
mod = webexec.load("https://raw.githubusercontent.com/user/repo/main/script.py")
|
|
70
|
+
print(mod.hello_world())
|
|
71
|
+
|
|
72
|
+
# Load with custom module name
|
|
73
|
+
utils = webexec.load("https://raw.githubusercontent.com/user/repo/main/utils.py", module_name="myutils")
|
|
74
|
+
utils.helper_function()
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### `webexec.fetch(url, *, timeout=10, cache=False)`
|
|
78
|
+
|
|
79
|
+
Return source code as string without executing.
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
import webexec
|
|
83
|
+
|
|
84
|
+
# Get source code
|
|
85
|
+
source = webexec.fetch("https://raw.githubusercontent.com/user/repo/main/script.py")
|
|
86
|
+
print(source)
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### `webexec.clear_cache()`
|
|
90
|
+
|
|
91
|
+
Clear the in-memory cache.
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
import webexec
|
|
95
|
+
|
|
96
|
+
# Clear cached content
|
|
97
|
+
webexec.clear_cache()
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Extra Functions
|
|
101
|
+
|
|
102
|
+
### `webexec.watch(url, interval=60)`
|
|
103
|
+
|
|
104
|
+
Re-fetch and re-run a script every X seconds automatically.
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
import webexec
|
|
108
|
+
import time
|
|
109
|
+
|
|
110
|
+
# Watch a script every 30 seconds
|
|
111
|
+
thread = webexec.watch("https://raw.githubusercontent.com/user/repo/main/monitor.py", interval=30)
|
|
112
|
+
|
|
113
|
+
# Keep the main thread alive
|
|
114
|
+
try:
|
|
115
|
+
while True:
|
|
116
|
+
time.sleep(1)
|
|
117
|
+
except KeyboardInterrupt:
|
|
118
|
+
print("Stopped watching")
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### `webexec.run_all([url1, url2, url3])`
|
|
122
|
+
|
|
123
|
+
Fetch and run multiple scripts in order.
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
import webexec
|
|
127
|
+
|
|
128
|
+
# Run multiple scripts
|
|
129
|
+
urls = [
|
|
130
|
+
"https://raw.githubusercontent.com/user/repo/main/setup.py",
|
|
131
|
+
"https://raw.githubusercontent.com/user/repo/main/main.py",
|
|
132
|
+
"https://raw.githubusercontent.com/user/repo/main/cleanup.py"
|
|
133
|
+
]
|
|
134
|
+
webexec.run_all(urls)
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### `webexec.preview(url)`
|
|
138
|
+
|
|
139
|
+
Print the source code without executing it.
|
|
140
|
+
|
|
141
|
+
```python
|
|
142
|
+
import webexec
|
|
143
|
+
|
|
144
|
+
# Preview a script
|
|
145
|
+
webexec.preview("https://raw.githubusercontent.com/user/repo/main/script.py")
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### `webexec.install_imports(url, *, timeout=10, cache=False, upgrade=False, user=False)`
|
|
149
|
+
|
|
150
|
+
Detect and install missing imports from a remote script.
|
|
151
|
+
|
|
152
|
+
```python
|
|
153
|
+
import webexec
|
|
154
|
+
|
|
155
|
+
# Install missing dependencies
|
|
156
|
+
installed = webexec.install_imports("https://raw.githubusercontent.com/user/repo/main/script.py")
|
|
157
|
+
print(f"Installed packages: {installed}")
|
|
158
|
+
|
|
159
|
+
# Install with upgrade and user flags
|
|
160
|
+
webexec.install_imports("https://raw.githubusercontent.com/user/repo/main/script.py",
|
|
161
|
+
upgrade=True, user=True)
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## URL Examples
|
|
165
|
+
|
|
166
|
+
The package works with any raw URL that returns Python code:
|
|
167
|
+
|
|
168
|
+
### GitHub Raw URLs
|
|
169
|
+
```python
|
|
170
|
+
webexec.run("https://raw.githubusercontent.com/username/repository/main/script.py")
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### Pastebin Raw URLs
|
|
174
|
+
```python
|
|
175
|
+
webexec.run("https://pastebin.com/raw/abc123def")
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### Any Raw URL
|
|
179
|
+
```python
|
|
180
|
+
webexec.run("https://example.com/files/myscript.py")
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## Error Handling
|
|
184
|
+
|
|
185
|
+
The package raises clear `RuntimeError` exceptions when:
|
|
186
|
+
|
|
187
|
+
- URL is not reachable
|
|
188
|
+
- HTTP status code is not 200
|
|
189
|
+
- Content is not valid UTF-8 text
|
|
190
|
+
- Network errors occur
|
|
191
|
+
|
|
192
|
+
```python
|
|
193
|
+
import webexec
|
|
194
|
+
|
|
195
|
+
try:
|
|
196
|
+
webexec.run("https://example.com/nonexistent.py")
|
|
197
|
+
except RuntimeError as e:
|
|
198
|
+
print(f"Error: {e}")
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
## Caching
|
|
202
|
+
|
|
203
|
+
Enable caching to avoid re-fetching the same URL:
|
|
204
|
+
|
|
205
|
+
```python
|
|
206
|
+
import webexec
|
|
207
|
+
|
|
208
|
+
# First call fetches from URL
|
|
209
|
+
webexec.run("https://example.com/script.py", cache=True)
|
|
210
|
+
|
|
211
|
+
# Subsequent calls use cached content
|
|
212
|
+
webexec.run("https://example.com/script.py", cache=True)
|
|
213
|
+
|
|
214
|
+
# Clear cache when needed
|
|
215
|
+
webexec.clear_cache()
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
## License
|
|
219
|
+
|
|
220
|
+
MIT License - see LICENSE file for details.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
webexec/__init__.py,sha256=LhILUFD_Ib5VAQqa1t9jrg5kfwQNr7iSZ9wGK9KVD8k,536
|
|
2
|
+
webexec/cache.py,sha256=ImI5I7-mlJPLAnw55kfbiUGAjJC1--GIRWG9QfwrOQE,1035
|
|
3
|
+
webexec/executor.py,sha256=VERMIQA3agATkA1fJc48xddBahAZardRKfl-gEPK7H8,3105
|
|
4
|
+
webexec/fetcher.py,sha256=orQrLdehXstNEup8VkBH-UgB919TAnkT3vlz7IN-hbs,1653
|
|
5
|
+
webexec/utils.py,sha256=bQorcQAQ-5b41dy1nAsIXB0LKA02BrwBAva_1bAwaVA,5537
|
|
6
|
+
webexec-1.0.0.dist-info/licenses/LICENSE,sha256=EQFzylAztx6xRhAM1HnQnQaYIWrtwYCnxI8E16HuwmI,1065
|
|
7
|
+
webexec-1.0.0.dist-info/METADATA,sha256=GeqHRy88Liu7Cf2wccY6cEb0Vf8sDE5pBbCGVZ0Ki8I,5390
|
|
8
|
+
webexec-1.0.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
9
|
+
webexec-1.0.0.dist-info/top_level.txt,sha256=0Kc1EAv6R1AY_pDcPdwTHXUjAUO1fmyS2I-glb2G0QU,8
|
|
10
|
+
webexec-1.0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 ZeroTeam
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
webexec
|