zexus 1.7.1 → 1.8.0
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.
- package/README.md +26 -3
- package/package.json +1 -1
- package/src/__init__.py +7 -0
- package/src/zexus/__init__.py +1 -1
- package/src/zexus/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/capability_system.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/debug_sanitizer.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/environment.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/error_reporter.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/input_validation.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/lexer.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/module_cache.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/module_manager.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/object.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/security.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/security_enforcement.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/syntax_validator.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/zexus_ast.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/zexus_token.cpython-312.pyc +0 -0
- package/src/zexus/access_control_system/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/zexus/access_control_system/__pycache__/access_control.cpython-312.pyc +0 -0
- package/src/zexus/advanced_types.py +17 -2
- package/src/zexus/blockchain/__init__.py +411 -0
- package/src/zexus/blockchain/accelerator.py +1187 -0
- package/src/zexus/blockchain/chain.py +660 -0
- package/src/zexus/blockchain/consensus.py +821 -0
- package/src/zexus/blockchain/contract_vm.py +1425 -0
- package/src/zexus/blockchain/crypto.py +79 -14
- package/src/zexus/blockchain/events.py +526 -0
- package/src/zexus/blockchain/loadtest.py +721 -0
- package/src/zexus/blockchain/monitoring.py +350 -0
- package/src/zexus/blockchain/mpt.py +716 -0
- package/src/zexus/blockchain/multichain.py +951 -0
- package/src/zexus/blockchain/multiprocess_executor.py +338 -0
- package/src/zexus/blockchain/network.py +886 -0
- package/src/zexus/blockchain/node.py +666 -0
- package/src/zexus/blockchain/rpc.py +1203 -0
- package/src/zexus/blockchain/rust_bridge.py +485 -0
- package/src/zexus/blockchain/storage.py +423 -0
- package/src/zexus/blockchain/tokens.py +750 -0
- package/src/zexus/blockchain/upgradeable.py +1004 -0
- package/src/zexus/blockchain/verification.py +1602 -0
- package/src/zexus/blockchain/wallet.py +621 -0
- package/src/zexus/cli/__pycache__/main.cpython-312.pyc +0 -0
- package/src/zexus/cli/main.py +300 -20
- package/src/zexus/cli/zpm.py +1 -1
- package/src/zexus/compiler/__pycache__/bytecode.cpython-312.pyc +0 -0
- package/src/zexus/compiler/__pycache__/lexer.cpython-312.pyc +0 -0
- package/src/zexus/compiler/__pycache__/parser.cpython-312.pyc +0 -0
- package/src/zexus/compiler/__pycache__/semantic.cpython-312.pyc +0 -0
- package/src/zexus/compiler/__pycache__/zexus_ast.cpython-312.pyc +0 -0
- package/src/zexus/compiler/lexer.py +10 -5
- package/src/zexus/concurrency_system.py +79 -0
- package/src/zexus/config.py +54 -0
- package/src/zexus/crypto_bridge.py +244 -8
- package/src/zexus/dap/__init__.py +10 -0
- package/src/zexus/dap/__main__.py +4 -0
- package/src/zexus/dap/dap_server.py +391 -0
- package/src/zexus/dap/debug_engine.py +298 -0
- package/src/zexus/environment.py +10 -1
- package/src/zexus/evaluator/__pycache__/bytecode_compiler.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/core.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/expressions.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/functions.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/resource_limiter.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/statements.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/unified_execution.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/utils.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/bytecode_compiler.py +441 -37
- package/src/zexus/evaluator/core.py +560 -49
- package/src/zexus/evaluator/expressions.py +122 -49
- package/src/zexus/evaluator/functions.py +417 -16
- package/src/zexus/evaluator/statements.py +521 -118
- package/src/zexus/evaluator/unified_execution.py +573 -72
- package/src/zexus/evaluator/utils.py +14 -2
- package/src/zexus/event_loop.py +186 -0
- package/src/zexus/lexer.py +742 -486
- package/src/zexus/lsp/__init__.py +1 -1
- package/src/zexus/lsp/definition_provider.py +163 -9
- package/src/zexus/lsp/server.py +22 -8
- package/src/zexus/lsp/symbol_provider.py +182 -9
- package/src/zexus/module_cache.py +237 -9
- package/src/zexus/object.py +64 -6
- package/src/zexus/parser/__pycache__/parser.cpython-312.pyc +0 -0
- package/src/zexus/parser/__pycache__/strategy_context.cpython-312.pyc +0 -0
- package/src/zexus/parser/__pycache__/strategy_structural.cpython-312.pyc +0 -0
- package/src/zexus/parser/parser.py +786 -285
- package/src/zexus/parser/strategy_context.py +407 -66
- package/src/zexus/parser/strategy_structural.py +117 -19
- package/src/zexus/persistence.py +15 -1
- package/src/zexus/renderer/__init__.py +15 -0
- package/src/zexus/renderer/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/zexus/renderer/__pycache__/backend.cpython-312.pyc +0 -0
- package/src/zexus/renderer/__pycache__/canvas.cpython-312.pyc +0 -0
- package/src/zexus/renderer/__pycache__/color_system.cpython-312.pyc +0 -0
- package/src/zexus/renderer/__pycache__/layout.cpython-312.pyc +0 -0
- package/src/zexus/renderer/__pycache__/main_renderer.cpython-312.pyc +0 -0
- package/src/zexus/renderer/__pycache__/painter.cpython-312.pyc +0 -0
- package/src/zexus/renderer/tk_backend.py +208 -0
- package/src/zexus/renderer/web_backend.py +260 -0
- package/src/zexus/runtime/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/zexus/runtime/__pycache__/async_runtime.cpython-312.pyc +0 -0
- package/src/zexus/runtime/__pycache__/load_manager.cpython-312.pyc +0 -0
- package/src/zexus/runtime/file_flags.py +137 -0
- package/src/zexus/safety/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/zexus/safety/__pycache__/memory_safety.cpython-312.pyc +0 -0
- package/src/zexus/security.py +424 -34
- package/src/zexus/stdlib/fs.py +23 -18
- package/src/zexus/stdlib/http.py +289 -186
- package/src/zexus/stdlib/sockets.py +207 -163
- package/src/zexus/stdlib/websockets.py +282 -0
- package/src/zexus/stdlib_integration.py +369 -2
- package/src/zexus/strategy_recovery.py +6 -3
- package/src/zexus/type_checker.py +423 -0
- package/src/zexus/virtual_filesystem.py +189 -2
- package/src/zexus/vm/__init__.py +113 -3
- package/src/zexus/vm/__pycache__/async_optimizer.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/bytecode.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/bytecode_converter.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/cache.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/compiler.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/gas_metering.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/jit.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/parallel_vm.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/vm.cpython-312.pyc +0 -0
- package/src/zexus/vm/async_optimizer.py +14 -1
- package/src/zexus/vm/binary_bytecode.py +659 -0
- package/src/zexus/vm/bytecode.py +28 -1
- package/src/zexus/vm/bytecode_converter.py +26 -12
- package/src/zexus/vm/cabi.c +1985 -0
- package/src/zexus/vm/cabi.cpython-312-x86_64-linux-gnu.so +0 -0
- package/src/zexus/vm/cabi.h +127 -0
- package/src/zexus/vm/cache.py +557 -17
- package/src/zexus/vm/compiler.py +703 -5
- package/src/zexus/vm/fastops.c +13861 -0
- package/src/zexus/vm/fastops.cpython-312-x86_64-linux-gnu.so +0 -0
- package/src/zexus/vm/fastops.pyx +288 -0
- package/src/zexus/vm/gas_metering.py +52 -11
- package/src/zexus/vm/jit.py +83 -2
- package/src/zexus/vm/native_jit_backend.py +1816 -0
- package/src/zexus/vm/native_runtime.cpp +1388 -0
- package/src/zexus/vm/native_runtime.cpython-312-x86_64-linux-gnu.so +0 -0
- package/src/zexus/vm/optimizer.py +161 -11
- package/src/zexus/vm/parallel_vm.py +118 -42
- package/src/zexus/vm/peephole_optimizer.py +82 -4
- package/src/zexus/vm/profiler.py +38 -18
- package/src/zexus/vm/register_allocator.py +16 -5
- package/src/zexus/vm/register_vm.py +8 -5
- package/src/zexus/vm/vm.py +3589 -588
- package/src/zexus/vm/wasm_compiler.py +658 -0
- package/src/zexus/zexus_ast.py +63 -11
- package/src/zexus/zexus_token.py +13 -5
- package/src/zexus/zpm/installer.py +55 -15
- package/src/zexus/zpm/package_manager.py +1 -1
- package/src/zexus/zpm/registry.py +257 -28
- package/src/zexus.egg-info/PKG-INFO +30 -4
- package/src/zexus.egg-info/SOURCES.txt +133 -9
- package/src/zexus.egg-info/entry_points.txt +1 -0
- package/src/zexus.egg-info/requires.txt +4 -0
package/src/zexus/stdlib/fs.py
CHANGED
|
@@ -154,6 +154,22 @@ class FileSystemModule:
|
|
|
154
154
|
|
|
155
155
|
@staticmethod
|
|
156
156
|
def mkdir(path: str, parents: bool = True) -> None:
|
|
157
|
+
"""Create directory."""
|
|
158
|
+
validated_path = FileSystemModule._validate_path(path, "mkdir")
|
|
159
|
+
Path(validated_path).mkdir(parents=parents, exist_ok=True)
|
|
160
|
+
|
|
161
|
+
@staticmethod
|
|
162
|
+
def rmdir(path: str, recursive: bool = False) -> None:
|
|
163
|
+
"""Remove directory."""
|
|
164
|
+
validated_path = FileSystemModule._validate_path(path, "rmdir")
|
|
165
|
+
if recursive:
|
|
166
|
+
shutil.rmtree(validated_path)
|
|
167
|
+
else:
|
|
168
|
+
os.rmdir(validated_path)
|
|
169
|
+
|
|
170
|
+
@staticmethod
|
|
171
|
+
def remove(path: str) -> None:
|
|
172
|
+
"""Remove file."""
|
|
157
173
|
validated_path = FileSystemModule._validate_path(path, "remove")
|
|
158
174
|
os.remove(validated_path)
|
|
159
175
|
|
|
@@ -169,32 +185,21 @@ class FileSystemModule:
|
|
|
169
185
|
"""Copy file."""
|
|
170
186
|
validated_src = FileSystemModule._validate_path(src, "copy_source")
|
|
171
187
|
validated_dst = FileSystemModule._validate_path(dst, "copy_dest")
|
|
172
|
-
shutil.copy2(validated_src,
|
|
173
|
-
@staticmethod
|
|
174
|
-
def remove(path: str) -> None:
|
|
175
|
-
"""Remove file."""
|
|
176
|
-
os.remove(path)
|
|
177
|
-
|
|
178
|
-
@staticmethod
|
|
179
|
-
def rename(old_path: str, new_path: str) -> None:
|
|
180
|
-
"""Rename/move file or directory."""
|
|
181
|
-
os.rename(old_path, new_path)
|
|
182
|
-
|
|
183
|
-
@staticmethod
|
|
184
|
-
def copy_file(src: str, dst: str) -> None:
|
|
185
|
-
"""Copy file."""
|
|
186
|
-
shutil.copy2(src, dst)
|
|
188
|
+
shutil.copy2(validated_src, validated_dst)
|
|
187
189
|
|
|
188
190
|
@staticmethod
|
|
189
191
|
def copy_dir(src: str, dst: str) -> None:
|
|
190
192
|
"""Copy directory recursively."""
|
|
191
|
-
|
|
192
|
-
|
|
193
|
+
validated_src = FileSystemModule._validate_path(src, "copy_dir_source")
|
|
194
|
+
validated_dst = FileSystemModule._validate_path(dst, "copy_dir_dest")
|
|
195
|
+
shutil.copytree(validated_src, validated_dst, dirs_exist_ok=True)
|
|
193
196
|
|
|
194
197
|
@staticmethod
|
|
195
198
|
def list_dir(path: str = '.') -> List[str]:
|
|
196
199
|
"""List directory contents."""
|
|
197
|
-
|
|
200
|
+
validated_path = FileSystemModule._validate_path(path, "list_dir")
|
|
201
|
+
return os.listdir(validated_path)
|
|
202
|
+
|
|
198
203
|
|
|
199
204
|
@staticmethod
|
|
200
205
|
def walk(path: str) -> List[Dict[str, Any]]:
|
package/src/zexus/stdlib/http.py
CHANGED
|
@@ -1,214 +1,317 @@
|
|
|
1
|
-
"""HTTP module for Zexus standard library.
|
|
1
|
+
"""HTTP module for Zexus standard library.
|
|
2
|
+
|
|
3
|
+
Provides sync + async HTTP client operations.
|
|
4
|
+
|
|
5
|
+
Primary backend: **httpx** (true async, connection pooling, HTTP/2 ready)
|
|
6
|
+
Fallback backend: ``urllib.request`` (when httpx is not installed)
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import json as json_lib
|
|
10
|
+
from typing import Dict, Any, Optional, List
|
|
11
|
+
from concurrent.futures import ThreadPoolExecutor, Future
|
|
12
|
+
|
|
13
|
+
# ---------------------------------------------------------------------------
|
|
14
|
+
# Backend detection
|
|
15
|
+
# ---------------------------------------------------------------------------
|
|
16
|
+
try:
|
|
17
|
+
import httpx as _httpx
|
|
18
|
+
_HTTPX_AVAILABLE = True
|
|
19
|
+
except ImportError:
|
|
20
|
+
_HTTPX_AVAILABLE = False
|
|
2
21
|
|
|
3
22
|
import urllib.request
|
|
4
23
|
import urllib.parse
|
|
5
24
|
import urllib.error
|
|
6
|
-
import json as json_lib
|
|
7
|
-
from typing import Dict, Any, Optional
|
|
8
25
|
|
|
9
26
|
|
|
27
|
+
# ---------------------------------------------------------------------------
|
|
28
|
+
# Shared clients (lazy singletons)
|
|
29
|
+
# ---------------------------------------------------------------------------
|
|
30
|
+
_sync_client: Optional[Any] = None
|
|
31
|
+
_async_client: Optional[Any] = None
|
|
32
|
+
_executor = ThreadPoolExecutor(max_workers=8, thread_name_prefix="zexus-http")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _get_sync_client():
|
|
36
|
+
"""Return (or create) the module-level httpx.Client with pooling."""
|
|
37
|
+
global _sync_client
|
|
38
|
+
if _sync_client is None and _HTTPX_AVAILABLE:
|
|
39
|
+
_sync_client = _httpx.Client(
|
|
40
|
+
follow_redirects=True,
|
|
41
|
+
timeout=_httpx.Timeout(30.0, connect=10.0),
|
|
42
|
+
limits=_httpx.Limits(max_connections=20, max_keepalive_connections=8),
|
|
43
|
+
)
|
|
44
|
+
return _sync_client
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _get_async_client():
|
|
48
|
+
"""Return (or create) the module-level httpx.AsyncClient."""
|
|
49
|
+
global _async_client
|
|
50
|
+
if _async_client is None and _HTTPX_AVAILABLE:
|
|
51
|
+
_async_client = _httpx.AsyncClient(
|
|
52
|
+
follow_redirects=True,
|
|
53
|
+
timeout=_httpx.Timeout(30.0, connect=10.0),
|
|
54
|
+
limits=_httpx.Limits(max_connections=20, max_keepalive_connections=8),
|
|
55
|
+
)
|
|
56
|
+
return _async_client
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
# ---------------------------------------------------------------------------
|
|
60
|
+
# Core request functions
|
|
61
|
+
# ---------------------------------------------------------------------------
|
|
62
|
+
|
|
63
|
+
def _httpx_request(method: str, url: str, data: bytes = None,
|
|
64
|
+
headers: Dict[str, str] = None, timeout: int = 30) -> Dict[str, Any]:
|
|
65
|
+
"""Execute an HTTP request via httpx (pooled, keep-alive)."""
|
|
66
|
+
client = _get_sync_client()
|
|
67
|
+
try:
|
|
68
|
+
resp = client.request(
|
|
69
|
+
method, url,
|
|
70
|
+
content=data,
|
|
71
|
+
headers=headers or {},
|
|
72
|
+
timeout=timeout,
|
|
73
|
+
)
|
|
74
|
+
return {
|
|
75
|
+
"status": resp.status_code,
|
|
76
|
+
"headers": dict(resp.headers),
|
|
77
|
+
"body": resp.text,
|
|
78
|
+
}
|
|
79
|
+
except Exception as exc:
|
|
80
|
+
return {"status": 0, "headers": {}, "body": "", "error": str(exc)}
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _urllib_request(method: str, url: str, data: bytes = None,
|
|
84
|
+
headers: Dict[str, str] = None, timeout: int = 30) -> Dict[str, Any]:
|
|
85
|
+
"""Fallback: single-use urllib request."""
|
|
86
|
+
req = urllib.request.Request(url, data=data, headers=headers or {}, method=method)
|
|
87
|
+
try:
|
|
88
|
+
with urllib.request.urlopen(req, timeout=timeout) as response:
|
|
89
|
+
body = response.read().decode("utf-8", errors="replace")
|
|
90
|
+
return {
|
|
91
|
+
"status": response.status,
|
|
92
|
+
"headers": dict(response.headers),
|
|
93
|
+
"body": body,
|
|
94
|
+
}
|
|
95
|
+
except urllib.error.HTTPError as e:
|
|
96
|
+
try:
|
|
97
|
+
error_body = e.read().decode("utf-8", errors="replace")
|
|
98
|
+
except Exception:
|
|
99
|
+
error_body = ""
|
|
100
|
+
return {
|
|
101
|
+
"status": e.code,
|
|
102
|
+
"headers": dict(e.headers),
|
|
103
|
+
"body": error_body,
|
|
104
|
+
"error": str(e),
|
|
105
|
+
}
|
|
106
|
+
except Exception as e:
|
|
107
|
+
return {"status": 0, "headers": {}, "body": "", "error": str(e)}
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _do_request(method: str, url: str, data: bytes = None,
|
|
111
|
+
headers: Dict[str, str] = None, timeout: int = 30) -> Dict[str, Any]:
|
|
112
|
+
"""Primary dispatcher — httpx if available, else urllib."""
|
|
113
|
+
if _HTTPX_AVAILABLE:
|
|
114
|
+
return _httpx_request(method, url, data, headers, timeout)
|
|
115
|
+
return _urllib_request(method, url, data, headers, timeout)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
# ---------------------------------------------------------------------------
|
|
119
|
+
# Async request via background event loop (true async when httpx available)
|
|
120
|
+
# ---------------------------------------------------------------------------
|
|
121
|
+
|
|
122
|
+
async def _httpx_async_request(method: str, url: str, data: bytes = None,
|
|
123
|
+
headers: Dict[str, str] = None,
|
|
124
|
+
timeout: int = 30) -> Dict[str, Any]:
|
|
125
|
+
"""True async HTTP request via httpx.AsyncClient."""
|
|
126
|
+
client = _get_async_client()
|
|
127
|
+
try:
|
|
128
|
+
resp = await client.request(
|
|
129
|
+
method, url,
|
|
130
|
+
content=data,
|
|
131
|
+
headers=headers or {},
|
|
132
|
+
timeout=timeout,
|
|
133
|
+
)
|
|
134
|
+
return {
|
|
135
|
+
"status": resp.status_code,
|
|
136
|
+
"headers": dict(resp.headers),
|
|
137
|
+
"body": resp.text,
|
|
138
|
+
}
|
|
139
|
+
except Exception as exc:
|
|
140
|
+
return {"status": 0, "headers": {}, "body": "", "error": str(exc)}
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
# ---------------------------------------------------------------------------
|
|
144
|
+
# HttpModule (public API — unchanged signatures)
|
|
145
|
+
# ---------------------------------------------------------------------------
|
|
146
|
+
|
|
10
147
|
class HttpModule:
|
|
11
|
-
"""Provides HTTP client operations."""
|
|
148
|
+
"""Provides HTTP client operations with connection pooling and async support."""
|
|
149
|
+
|
|
150
|
+
@staticmethod
|
|
151
|
+
def _prepare_body(data: Any, headers: Dict[str, str], json: bool = False) -> bytes:
|
|
152
|
+
"""Encode request body and set Content-Type when necessary."""
|
|
153
|
+
if data is None:
|
|
154
|
+
return None
|
|
155
|
+
if json:
|
|
156
|
+
headers.setdefault("Content-Type", "application/json")
|
|
157
|
+
return json_lib.dumps(data).encode("utf-8")
|
|
158
|
+
if isinstance(data, str):
|
|
159
|
+
return data.encode("utf-8")
|
|
160
|
+
if isinstance(data, dict):
|
|
161
|
+
return urllib.parse.urlencode(data).encode("utf-8")
|
|
162
|
+
if isinstance(data, bytes):
|
|
163
|
+
return data
|
|
164
|
+
return str(data).encode("utf-8")
|
|
165
|
+
|
|
166
|
+
# ------------------------------------------------------------------
|
|
167
|
+
# Synchronous API
|
|
168
|
+
# ------------------------------------------------------------------
|
|
12
169
|
|
|
13
170
|
@staticmethod
|
|
14
171
|
def get(url: str, headers: Optional[Dict[str, str]] = None, timeout: int = 30) -> Dict[str, Any]:
|
|
15
|
-
""
|
|
16
|
-
if headers is None:
|
|
17
|
-
headers = {}
|
|
18
|
-
|
|
19
|
-
req = urllib.request.Request(url, headers=headers, method='GET')
|
|
20
|
-
|
|
21
|
-
try:
|
|
22
|
-
with urllib.request.urlopen(req, timeout=timeout) as response:
|
|
23
|
-
body = response.read().decode('utf-8')
|
|
24
|
-
return {
|
|
25
|
-
'status': response.status,
|
|
26
|
-
'headers': dict(response.headers),
|
|
27
|
-
'body': body
|
|
28
|
-
}
|
|
29
|
-
except urllib.error.HTTPError as e:
|
|
30
|
-
try:
|
|
31
|
-
error_body = e.read().decode('utf-8')
|
|
32
|
-
except UnicodeDecodeError:
|
|
33
|
-
error_body = e.read().decode('utf-8', errors='replace')
|
|
34
|
-
return {
|
|
35
|
-
'status': e.code,
|
|
36
|
-
'headers': dict(e.headers),
|
|
37
|
-
'body': error_body,
|
|
38
|
-
'error': str(e)
|
|
39
|
-
}
|
|
40
|
-
except Exception as e:
|
|
41
|
-
return {
|
|
42
|
-
'status': 0,
|
|
43
|
-
'headers': {},
|
|
44
|
-
'body': '',
|
|
45
|
-
'error': str(e)
|
|
46
|
-
}
|
|
172
|
+
return _do_request("GET", url, headers=headers or {}, timeout=timeout)
|
|
47
173
|
|
|
48
174
|
@staticmethod
|
|
49
|
-
def post(url: str, data: Any = None, headers: Optional[Dict[str, str]] = None,
|
|
175
|
+
def post(url: str, data: Any = None, headers: Optional[Dict[str, str]] = None,
|
|
50
176
|
json: bool = False, timeout: int = 30) -> Dict[str, Any]:
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
if json and data is not None:
|
|
56
|
-
data = json_lib.dumps(data).encode('utf-8')
|
|
57
|
-
headers['Content-Type'] = 'application/json'
|
|
58
|
-
elif isinstance(data, str):
|
|
59
|
-
data = data.encode('utf-8')
|
|
60
|
-
elif isinstance(data, dict):
|
|
61
|
-
data = urllib.parse.urlencode(data).encode('utf-8')
|
|
62
|
-
|
|
63
|
-
req = urllib.request.Request(url, data=data, headers=headers, method='POST')
|
|
64
|
-
|
|
65
|
-
try:
|
|
66
|
-
with urllib.request.urlopen(req, timeout=timeout) as response:
|
|
67
|
-
body = response.read().decode('utf-8')
|
|
68
|
-
return {
|
|
69
|
-
'status': response.status,
|
|
70
|
-
'headers': dict(response.headers),
|
|
71
|
-
'body': body
|
|
72
|
-
}
|
|
73
|
-
except urllib.error.HTTPError as e:
|
|
74
|
-
try:
|
|
75
|
-
error_body = e.read().decode('utf-8')
|
|
76
|
-
except UnicodeDecodeError:
|
|
77
|
-
error_body = e.read().decode('utf-8', errors='replace')
|
|
78
|
-
return {
|
|
79
|
-
'status': e.code,
|
|
80
|
-
'headers': dict(e.headers),
|
|
81
|
-
'body': error_body,
|
|
82
|
-
'error': str(e)
|
|
83
|
-
}
|
|
84
|
-
except Exception as e:
|
|
85
|
-
return {
|
|
86
|
-
'status': 0,
|
|
87
|
-
'headers': {},
|
|
88
|
-
'body': '',
|
|
89
|
-
'error': str(e)
|
|
90
|
-
}
|
|
177
|
+
hdrs = dict(headers or {})
|
|
178
|
+
body = HttpModule._prepare_body(data, hdrs, json)
|
|
179
|
+
return _do_request("POST", url, data=body, headers=hdrs, timeout=timeout)
|
|
91
180
|
|
|
92
181
|
@staticmethod
|
|
93
182
|
def put(url: str, data: Any = None, headers: Optional[Dict[str, str]] = None,
|
|
94
183
|
json: bool = False, timeout: int = 30) -> Dict[str, Any]:
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
if json and data is not None:
|
|
100
|
-
data = json_lib.dumps(data).encode('utf-8')
|
|
101
|
-
headers['Content-Type'] = 'application/json'
|
|
102
|
-
elif isinstance(data, str):
|
|
103
|
-
data = data.encode('utf-8')
|
|
104
|
-
elif isinstance(data, dict):
|
|
105
|
-
data = urllib.parse.urlencode(data).encode('utf-8')
|
|
106
|
-
|
|
107
|
-
req = urllib.request.Request(url, data=data, headers=headers, method='PUT')
|
|
108
|
-
|
|
109
|
-
try:
|
|
110
|
-
with urllib.request.urlopen(req, timeout=timeout) as response:
|
|
111
|
-
body = response.read().decode('utf-8')
|
|
112
|
-
return {
|
|
113
|
-
'status': response.status,
|
|
114
|
-
'headers': dict(response.headers),
|
|
115
|
-
'body': body
|
|
116
|
-
}
|
|
117
|
-
except urllib.error.HTTPError as e:
|
|
118
|
-
try:
|
|
119
|
-
error_body = e.read().decode('utf-8')
|
|
120
|
-
except UnicodeDecodeError:
|
|
121
|
-
error_body = e.read().decode('utf-8', errors='replace')
|
|
122
|
-
return {
|
|
123
|
-
'status': e.code,
|
|
124
|
-
'headers': dict(e.headers),
|
|
125
|
-
'body': error_body,
|
|
126
|
-
'error': str(e)
|
|
127
|
-
}
|
|
128
|
-
except Exception as e:
|
|
129
|
-
return {
|
|
130
|
-
'status': 0,
|
|
131
|
-
'headers': {},
|
|
132
|
-
'body': '',
|
|
133
|
-
'error': str(e)
|
|
134
|
-
}
|
|
184
|
+
hdrs = dict(headers or {})
|
|
185
|
+
body = HttpModule._prepare_body(data, hdrs, json)
|
|
186
|
+
return _do_request("PUT", url, data=body, headers=hdrs, timeout=timeout)
|
|
135
187
|
|
|
136
188
|
@staticmethod
|
|
137
189
|
def delete(url: str, headers: Optional[Dict[str, str]] = None, timeout: int = 30) -> Dict[str, Any]:
|
|
138
|
-
""
|
|
139
|
-
if headers is None:
|
|
140
|
-
headers = {}
|
|
141
|
-
|
|
142
|
-
req = urllib.request.Request(url, headers=headers, method='DELETE')
|
|
143
|
-
|
|
144
|
-
try:
|
|
145
|
-
with urllib.request.urlopen(req, timeout=timeout) as response:
|
|
146
|
-
body = response.read().decode('utf-8')
|
|
147
|
-
return {
|
|
148
|
-
'status': response.status,
|
|
149
|
-
'headers': dict(response.headers),
|
|
150
|
-
'body': body
|
|
151
|
-
}
|
|
152
|
-
except urllib.error.HTTPError as e:
|
|
153
|
-
try:
|
|
154
|
-
error_body = e.read().decode('utf-8')
|
|
155
|
-
except UnicodeDecodeError:
|
|
156
|
-
error_body = e.read().decode('utf-8', errors='replace')
|
|
157
|
-
return {
|
|
158
|
-
'status': e.code,
|
|
159
|
-
'headers': dict(e.headers),
|
|
160
|
-
'body': error_body,
|
|
161
|
-
'error': str(e)
|
|
162
|
-
}
|
|
163
|
-
except Exception as e:
|
|
164
|
-
return {
|
|
165
|
-
'status': 0,
|
|
166
|
-
'headers': {},
|
|
167
|
-
'body': '',
|
|
168
|
-
'error': str(e)
|
|
169
|
-
}
|
|
190
|
+
return _do_request("DELETE", url, headers=headers or {}, timeout=timeout)
|
|
170
191
|
|
|
171
192
|
@staticmethod
|
|
172
193
|
def request(method: str, url: str, data: Any = None, headers: Optional[Dict[str, str]] = None,
|
|
173
194
|
timeout: int = 30) -> Dict[str, Any]:
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
+
hdrs = dict(headers or {})
|
|
196
|
+
body = HttpModule._prepare_body(data, hdrs)
|
|
197
|
+
return _do_request(method.upper(), url, data=body, headers=hdrs, timeout=timeout)
|
|
198
|
+
|
|
199
|
+
# ------------------------------------------------------------------
|
|
200
|
+
# Async API — returns Future objects (compatible with existing callers)
|
|
201
|
+
# ------------------------------------------------------------------
|
|
202
|
+
|
|
203
|
+
@staticmethod
|
|
204
|
+
def async_get(url: str, headers: Optional[Dict[str, str]] = None, timeout: int = 30) -> Future:
|
|
205
|
+
if _HTTPX_AVAILABLE:
|
|
206
|
+
from .sockets import _get_bg_loop, _run_async
|
|
207
|
+
loop = _get_bg_loop()
|
|
208
|
+
import asyncio
|
|
209
|
+
return asyncio.run_coroutine_threadsafe(
|
|
210
|
+
_httpx_async_request("GET", url, headers=headers or {}, timeout=timeout), loop
|
|
211
|
+
)
|
|
212
|
+
return _executor.submit(HttpModule.get, url, headers, timeout)
|
|
213
|
+
|
|
214
|
+
@staticmethod
|
|
215
|
+
def async_post(url: str, data: Any = None, headers: Optional[Dict[str, str]] = None,
|
|
216
|
+
json: bool = False, timeout: int = 30) -> Future:
|
|
217
|
+
if _HTTPX_AVAILABLE:
|
|
218
|
+
from .sockets import _get_bg_loop
|
|
219
|
+
import asyncio
|
|
220
|
+
hdrs = dict(headers or {})
|
|
221
|
+
body = HttpModule._prepare_body(data, hdrs, json)
|
|
222
|
+
return asyncio.run_coroutine_threadsafe(
|
|
223
|
+
_httpx_async_request("POST", url, data=body, headers=hdrs, timeout=timeout),
|
|
224
|
+
_get_bg_loop()
|
|
225
|
+
)
|
|
226
|
+
return _executor.submit(HttpModule.post, url, data, headers, json, timeout)
|
|
227
|
+
|
|
228
|
+
@staticmethod
|
|
229
|
+
def async_put(url: str, data: Any = None, headers: Optional[Dict[str, str]] = None,
|
|
230
|
+
json: bool = False, timeout: int = 30) -> Future:
|
|
231
|
+
if _HTTPX_AVAILABLE:
|
|
232
|
+
from .sockets import _get_bg_loop
|
|
233
|
+
import asyncio
|
|
234
|
+
hdrs = dict(headers or {})
|
|
235
|
+
body = HttpModule._prepare_body(data, hdrs, json)
|
|
236
|
+
return asyncio.run_coroutine_threadsafe(
|
|
237
|
+
_httpx_async_request("PUT", url, data=body, headers=hdrs, timeout=timeout),
|
|
238
|
+
_get_bg_loop()
|
|
239
|
+
)
|
|
240
|
+
return _executor.submit(HttpModule.put, url, data, headers, json, timeout)
|
|
241
|
+
|
|
242
|
+
@staticmethod
|
|
243
|
+
def async_delete(url: str, headers: Optional[Dict[str, str]] = None, timeout: int = 30) -> Future:
|
|
244
|
+
if _HTTPX_AVAILABLE:
|
|
245
|
+
from .sockets import _get_bg_loop
|
|
246
|
+
import asyncio
|
|
247
|
+
return asyncio.run_coroutine_threadsafe(
|
|
248
|
+
_httpx_async_request("DELETE", url, headers=headers or {}, timeout=timeout),
|
|
249
|
+
_get_bg_loop()
|
|
250
|
+
)
|
|
251
|
+
return _executor.submit(HttpModule.delete, url, headers, timeout)
|
|
252
|
+
|
|
253
|
+
@staticmethod
|
|
254
|
+
def async_request(method: str, url: str, data: Any = None,
|
|
255
|
+
headers: Optional[Dict[str, str]] = None, timeout: int = 30) -> Future:
|
|
256
|
+
if _HTTPX_AVAILABLE:
|
|
257
|
+
from .sockets import _get_bg_loop
|
|
258
|
+
import asyncio
|
|
259
|
+
hdrs = dict(headers or {})
|
|
260
|
+
body = HttpModule._prepare_body(data, hdrs)
|
|
261
|
+
return asyncio.run_coroutine_threadsafe(
|
|
262
|
+
_httpx_async_request(method.upper(), url, data=body, headers=hdrs, timeout=timeout),
|
|
263
|
+
_get_bg_loop()
|
|
264
|
+
)
|
|
265
|
+
return _executor.submit(HttpModule.request, method, url, data, headers, timeout)
|
|
266
|
+
|
|
267
|
+
@staticmethod
|
|
268
|
+
def parallel_get(urls: List[str], headers: Optional[Dict[str, str]] = None,
|
|
269
|
+
timeout: int = 30) -> List[Dict[str, Any]]:
|
|
270
|
+
"""Execute multiple GET requests in parallel."""
|
|
271
|
+
if _HTTPX_AVAILABLE:
|
|
272
|
+
import asyncio
|
|
273
|
+
from .sockets import _get_bg_loop
|
|
274
|
+
|
|
275
|
+
async def _batch():
|
|
276
|
+
cl = _get_async_client()
|
|
277
|
+
tasks = [
|
|
278
|
+
cl.request("GET", u, headers=headers or {}, timeout=timeout)
|
|
279
|
+
for u in urls
|
|
280
|
+
]
|
|
281
|
+
results = await asyncio.gather(*tasks, return_exceptions=True)
|
|
282
|
+
out = []
|
|
283
|
+
for r in results:
|
|
284
|
+
if isinstance(r, Exception):
|
|
285
|
+
out.append({"status": 0, "headers": {}, "body": "", "error": str(r)})
|
|
286
|
+
else:
|
|
287
|
+
out.append({"status": r.status_code, "headers": dict(r.headers), "body": r.text})
|
|
288
|
+
return out
|
|
289
|
+
|
|
290
|
+
return asyncio.run_coroutine_threadsafe(_batch(), _get_bg_loop()).result()
|
|
291
|
+
|
|
292
|
+
futures = [_executor.submit(HttpModule.get, u, headers, timeout) for u in urls]
|
|
293
|
+
return [f.result() for f in futures]
|
|
294
|
+
|
|
295
|
+
@staticmethod
|
|
296
|
+
def close_pool():
|
|
297
|
+
"""Close pooled connections (call at shutdown)."""
|
|
298
|
+
global _sync_client, _async_client
|
|
299
|
+
if _sync_client:
|
|
195
300
|
try:
|
|
196
|
-
|
|
197
|
-
except
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
'error': str(e)
|
|
211
|
-
}
|
|
301
|
+
_sync_client.close()
|
|
302
|
+
except Exception:
|
|
303
|
+
pass
|
|
304
|
+
_sync_client = None
|
|
305
|
+
if _async_client:
|
|
306
|
+
try:
|
|
307
|
+
import asyncio
|
|
308
|
+
from .sockets import _get_bg_loop
|
|
309
|
+
async def _aclose():
|
|
310
|
+
await _async_client.aclose()
|
|
311
|
+
asyncio.run_coroutine_threadsafe(_aclose(), _get_bg_loop()).result(timeout=5)
|
|
312
|
+
except Exception:
|
|
313
|
+
pass
|
|
314
|
+
_async_client = None
|
|
212
315
|
|
|
213
316
|
|
|
214
317
|
# Export functions for easy access
|