zexus 1.7.1 → 1.7.2

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 (159) hide show
  1. package/README.md +3 -3
  2. package/package.json +1 -1
  3. package/src/__init__.py +7 -0
  4. package/src/zexus/__init__.py +1 -1
  5. package/src/zexus/__pycache__/__init__.cpython-312.pyc +0 -0
  6. package/src/zexus/__pycache__/capability_system.cpython-312.pyc +0 -0
  7. package/src/zexus/__pycache__/debug_sanitizer.cpython-312.pyc +0 -0
  8. package/src/zexus/__pycache__/environment.cpython-312.pyc +0 -0
  9. package/src/zexus/__pycache__/error_reporter.cpython-312.pyc +0 -0
  10. package/src/zexus/__pycache__/input_validation.cpython-312.pyc +0 -0
  11. package/src/zexus/__pycache__/lexer.cpython-312.pyc +0 -0
  12. package/src/zexus/__pycache__/module_cache.cpython-312.pyc +0 -0
  13. package/src/zexus/__pycache__/module_manager.cpython-312.pyc +0 -0
  14. package/src/zexus/__pycache__/object.cpython-312.pyc +0 -0
  15. package/src/zexus/__pycache__/security.cpython-312.pyc +0 -0
  16. package/src/zexus/__pycache__/security_enforcement.cpython-312.pyc +0 -0
  17. package/src/zexus/__pycache__/syntax_validator.cpython-312.pyc +0 -0
  18. package/src/zexus/__pycache__/zexus_ast.cpython-312.pyc +0 -0
  19. package/src/zexus/__pycache__/zexus_token.cpython-312.pyc +0 -0
  20. package/src/zexus/access_control_system/__pycache__/__init__.cpython-312.pyc +0 -0
  21. package/src/zexus/access_control_system/__pycache__/access_control.cpython-312.pyc +0 -0
  22. package/src/zexus/advanced_types.py +17 -2
  23. package/src/zexus/blockchain/__init__.py +411 -0
  24. package/src/zexus/blockchain/accelerator.py +1160 -0
  25. package/src/zexus/blockchain/chain.py +660 -0
  26. package/src/zexus/blockchain/consensus.py +821 -0
  27. package/src/zexus/blockchain/contract_vm.py +1019 -0
  28. package/src/zexus/blockchain/crypto.py +79 -14
  29. package/src/zexus/blockchain/events.py +526 -0
  30. package/src/zexus/blockchain/loadtest.py +721 -0
  31. package/src/zexus/blockchain/monitoring.py +350 -0
  32. package/src/zexus/blockchain/mpt.py +716 -0
  33. package/src/zexus/blockchain/multichain.py +951 -0
  34. package/src/zexus/blockchain/multiprocess_executor.py +338 -0
  35. package/src/zexus/blockchain/network.py +886 -0
  36. package/src/zexus/blockchain/node.py +666 -0
  37. package/src/zexus/blockchain/rpc.py +1203 -0
  38. package/src/zexus/blockchain/rust_bridge.py +421 -0
  39. package/src/zexus/blockchain/storage.py +423 -0
  40. package/src/zexus/blockchain/tokens.py +750 -0
  41. package/src/zexus/blockchain/upgradeable.py +1004 -0
  42. package/src/zexus/blockchain/verification.py +1602 -0
  43. package/src/zexus/blockchain/wallet.py +621 -0
  44. package/src/zexus/cli/__pycache__/main.cpython-312.pyc +0 -0
  45. package/src/zexus/cli/main.py +300 -20
  46. package/src/zexus/cli/zpm.py +1 -1
  47. package/src/zexus/compiler/__pycache__/bytecode.cpython-312.pyc +0 -0
  48. package/src/zexus/compiler/__pycache__/lexer.cpython-312.pyc +0 -0
  49. package/src/zexus/compiler/__pycache__/parser.cpython-312.pyc +0 -0
  50. package/src/zexus/compiler/__pycache__/semantic.cpython-312.pyc +0 -0
  51. package/src/zexus/compiler/__pycache__/zexus_ast.cpython-312.pyc +0 -0
  52. package/src/zexus/compiler/lexer.py +10 -5
  53. package/src/zexus/concurrency_system.py +79 -0
  54. package/src/zexus/config.py +54 -0
  55. package/src/zexus/crypto_bridge.py +244 -8
  56. package/src/zexus/dap/__init__.py +10 -0
  57. package/src/zexus/dap/__main__.py +4 -0
  58. package/src/zexus/dap/dap_server.py +391 -0
  59. package/src/zexus/dap/debug_engine.py +298 -0
  60. package/src/zexus/environment.py +10 -1
  61. package/src/zexus/evaluator/__pycache__/bytecode_compiler.cpython-312.pyc +0 -0
  62. package/src/zexus/evaluator/__pycache__/core.cpython-312.pyc +0 -0
  63. package/src/zexus/evaluator/__pycache__/expressions.cpython-312.pyc +0 -0
  64. package/src/zexus/evaluator/__pycache__/functions.cpython-312.pyc +0 -0
  65. package/src/zexus/evaluator/__pycache__/resource_limiter.cpython-312.pyc +0 -0
  66. package/src/zexus/evaluator/__pycache__/statements.cpython-312.pyc +0 -0
  67. package/src/zexus/evaluator/__pycache__/unified_execution.cpython-312.pyc +0 -0
  68. package/src/zexus/evaluator/__pycache__/utils.cpython-312.pyc +0 -0
  69. package/src/zexus/evaluator/bytecode_compiler.py +441 -37
  70. package/src/zexus/evaluator/core.py +560 -49
  71. package/src/zexus/evaluator/expressions.py +122 -49
  72. package/src/zexus/evaluator/functions.py +417 -16
  73. package/src/zexus/evaluator/statements.py +521 -118
  74. package/src/zexus/evaluator/unified_execution.py +573 -72
  75. package/src/zexus/evaluator/utils.py +14 -2
  76. package/src/zexus/event_loop.py +186 -0
  77. package/src/zexus/lexer.py +742 -486
  78. package/src/zexus/lsp/__init__.py +1 -1
  79. package/src/zexus/lsp/definition_provider.py +163 -9
  80. package/src/zexus/lsp/server.py +22 -8
  81. package/src/zexus/lsp/symbol_provider.py +182 -9
  82. package/src/zexus/module_cache.py +237 -9
  83. package/src/zexus/object.py +64 -6
  84. package/src/zexus/parser/__pycache__/parser.cpython-312.pyc +0 -0
  85. package/src/zexus/parser/__pycache__/strategy_context.cpython-312.pyc +0 -0
  86. package/src/zexus/parser/__pycache__/strategy_structural.cpython-312.pyc +0 -0
  87. package/src/zexus/parser/parser.py +786 -285
  88. package/src/zexus/parser/strategy_context.py +407 -66
  89. package/src/zexus/parser/strategy_structural.py +117 -19
  90. package/src/zexus/persistence.py +15 -1
  91. package/src/zexus/renderer/__init__.py +15 -0
  92. package/src/zexus/renderer/__pycache__/__init__.cpython-312.pyc +0 -0
  93. package/src/zexus/renderer/__pycache__/backend.cpython-312.pyc +0 -0
  94. package/src/zexus/renderer/__pycache__/canvas.cpython-312.pyc +0 -0
  95. package/src/zexus/renderer/__pycache__/color_system.cpython-312.pyc +0 -0
  96. package/src/zexus/renderer/__pycache__/layout.cpython-312.pyc +0 -0
  97. package/src/zexus/renderer/__pycache__/main_renderer.cpython-312.pyc +0 -0
  98. package/src/zexus/renderer/__pycache__/painter.cpython-312.pyc +0 -0
  99. package/src/zexus/renderer/tk_backend.py +208 -0
  100. package/src/zexus/renderer/web_backend.py +260 -0
  101. package/src/zexus/runtime/__pycache__/__init__.cpython-312.pyc +0 -0
  102. package/src/zexus/runtime/__pycache__/async_runtime.cpython-312.pyc +0 -0
  103. package/src/zexus/runtime/__pycache__/load_manager.cpython-312.pyc +0 -0
  104. package/src/zexus/runtime/file_flags.py +137 -0
  105. package/src/zexus/safety/__pycache__/__init__.cpython-312.pyc +0 -0
  106. package/src/zexus/safety/__pycache__/memory_safety.cpython-312.pyc +0 -0
  107. package/src/zexus/security.py +424 -34
  108. package/src/zexus/stdlib/fs.py +23 -18
  109. package/src/zexus/stdlib/http.py +289 -186
  110. package/src/zexus/stdlib/sockets.py +207 -163
  111. package/src/zexus/stdlib/websockets.py +282 -0
  112. package/src/zexus/stdlib_integration.py +369 -2
  113. package/src/zexus/strategy_recovery.py +6 -3
  114. package/src/zexus/type_checker.py +423 -0
  115. package/src/zexus/virtual_filesystem.py +189 -2
  116. package/src/zexus/vm/__init__.py +113 -3
  117. package/src/zexus/vm/__pycache__/async_optimizer.cpython-312.pyc +0 -0
  118. package/src/zexus/vm/__pycache__/bytecode.cpython-312.pyc +0 -0
  119. package/src/zexus/vm/__pycache__/bytecode_converter.cpython-312.pyc +0 -0
  120. package/src/zexus/vm/__pycache__/cache.cpython-312.pyc +0 -0
  121. package/src/zexus/vm/__pycache__/compiler.cpython-312.pyc +0 -0
  122. package/src/zexus/vm/__pycache__/gas_metering.cpython-312.pyc +0 -0
  123. package/src/zexus/vm/__pycache__/jit.cpython-312.pyc +0 -0
  124. package/src/zexus/vm/__pycache__/parallel_vm.cpython-312.pyc +0 -0
  125. package/src/zexus/vm/__pycache__/vm.cpython-312.pyc +0 -0
  126. package/src/zexus/vm/async_optimizer.py +14 -1
  127. package/src/zexus/vm/binary_bytecode.py +659 -0
  128. package/src/zexus/vm/bytecode.py +28 -1
  129. package/src/zexus/vm/bytecode_converter.py +26 -12
  130. package/src/zexus/vm/cabi.c +1985 -0
  131. package/src/zexus/vm/cabi.cpython-312-x86_64-linux-gnu.so +0 -0
  132. package/src/zexus/vm/cabi.h +127 -0
  133. package/src/zexus/vm/cache.py +557 -17
  134. package/src/zexus/vm/compiler.py +703 -5
  135. package/src/zexus/vm/fastops.c +15743 -0
  136. package/src/zexus/vm/fastops.cpython-312-x86_64-linux-gnu.so +0 -0
  137. package/src/zexus/vm/fastops.pyx +288 -0
  138. package/src/zexus/vm/gas_metering.py +50 -9
  139. package/src/zexus/vm/jit.py +83 -2
  140. package/src/zexus/vm/native_jit_backend.py +1816 -0
  141. package/src/zexus/vm/native_runtime.cpp +1388 -0
  142. package/src/zexus/vm/native_runtime.cpython-312-x86_64-linux-gnu.so +0 -0
  143. package/src/zexus/vm/optimizer.py +161 -11
  144. package/src/zexus/vm/parallel_vm.py +118 -42
  145. package/src/zexus/vm/peephole_optimizer.py +82 -4
  146. package/src/zexus/vm/profiler.py +38 -18
  147. package/src/zexus/vm/register_allocator.py +16 -5
  148. package/src/zexus/vm/register_vm.py +8 -5
  149. package/src/zexus/vm/vm.py +3411 -573
  150. package/src/zexus/vm/wasm_compiler.py +658 -0
  151. package/src/zexus/zexus_ast.py +63 -11
  152. package/src/zexus/zexus_token.py +13 -5
  153. package/src/zexus/zpm/installer.py +55 -15
  154. package/src/zexus/zpm/package_manager.py +1 -1
  155. package/src/zexus/zpm/registry.py +257 -28
  156. package/src/zexus.egg-info/PKG-INFO +7 -4
  157. package/src/zexus.egg-info/SOURCES.txt +116 -9
  158. package/src/zexus.egg-info/entry_points.txt +1 -0
  159. package/src/zexus.egg-info/requires.txt +4 -0
@@ -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, validated_
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
- validated_path = FileSystemModule._validate_path(path, "list_dir")
192
- return os.listdir(validated_c, dst)
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
- return os.listdir(path)
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]]:
@@ -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
- """Make HTTP GET request."""
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
- """Make HTTP POST request."""
52
- if headers is None:
53
- headers = {}
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
- """Make HTTP PUT request."""
96
- if headers is None:
97
- headers = {}
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
- """Make HTTP DELETE request."""
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
- """Make HTTP request with custom method."""
175
- if headers is None:
176
- headers = {}
177
-
178
- if data is not None:
179
- if isinstance(data, str):
180
- data = data.encode('utf-8')
181
- elif isinstance(data, dict):
182
- data = urllib.parse.urlencode(data).encode('utf-8')
183
-
184
- req = urllib.request.Request(url, data=data, headers=headers, method=method.upper())
185
-
186
- try:
187
- with urllib.request.urlopen(req, timeout=timeout) as response:
188
- body = response.read().decode('utf-8')
189
- return {
190
- 'status': response.status,
191
- 'headers': dict(response.headers),
192
- 'body': body
193
- }
194
- except urllib.error.HTTPError as e:
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
- error_body = e.read().decode('utf-8')
197
- except UnicodeDecodeError:
198
- error_body = e.read().decode('utf-8', errors='replace')
199
- return {
200
- 'status': e.code,
201
- 'headers': dict(e.headers),
202
- 'body': error_body,
203
- 'error': str(e)
204
- }
205
- except Exception as e:
206
- return {
207
- 'status': 0,
208
- 'headers': {},
209
- 'body': '',
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