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.
- package/README.md +3 -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 +1160 -0
- package/src/zexus/blockchain/chain.py +660 -0
- package/src/zexus/blockchain/consensus.py +821 -0
- package/src/zexus/blockchain/contract_vm.py +1019 -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 +421 -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 +15743 -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 +50 -9
- 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 +3411 -573
- 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 +7 -4
- package/src/zexus.egg-info/SOURCES.txt +116 -9
- package/src/zexus.egg-info/entry_points.txt +1 -0
- package/src/zexus.egg-info/requires.txt +4 -0
|
@@ -1,11 +1,52 @@
|
|
|
1
|
-
"""Socket/TCP primitives module for Zexus standard library.
|
|
1
|
+
"""Socket/TCP primitives module for Zexus standard library.
|
|
2
2
|
|
|
3
|
+
Uses ``asyncio`` for non-blocking I/O instead of one-thread-per-connection.
|
|
4
|
+
A background event-loop thread is shared across all sockets so callers that
|
|
5
|
+
aren't themselves running inside an asyncio loop get synchronous-looking
|
|
6
|
+
wrappers automatically.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import asyncio
|
|
3
10
|
import socket
|
|
4
11
|
import threading
|
|
5
12
|
import time
|
|
6
13
|
from typing import Callable, Optional, Dict, Any
|
|
7
14
|
|
|
8
15
|
|
|
16
|
+
# ---------------------------------------------------------------------------
|
|
17
|
+
# Shared background event loop (lazily created, one per interpreter)
|
|
18
|
+
# ---------------------------------------------------------------------------
|
|
19
|
+
_BG_LOOP: Optional[asyncio.AbstractEventLoop] = None
|
|
20
|
+
_BG_LOOP_LOCK = threading.Lock()
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _get_bg_loop() -> asyncio.AbstractEventLoop:
|
|
24
|
+
"""Return the shared background asyncio event loop, starting it if needed."""
|
|
25
|
+
global _BG_LOOP
|
|
26
|
+
if _BG_LOOP is not None and _BG_LOOP.is_running():
|
|
27
|
+
return _BG_LOOP
|
|
28
|
+
with _BG_LOOP_LOCK:
|
|
29
|
+
if _BG_LOOP is not None and _BG_LOOP.is_running():
|
|
30
|
+
return _BG_LOOP
|
|
31
|
+
loop = asyncio.new_event_loop()
|
|
32
|
+
|
|
33
|
+
def _run(l: asyncio.AbstractEventLoop):
|
|
34
|
+
asyncio.set_event_loop(l)
|
|
35
|
+
l.run_forever()
|
|
36
|
+
|
|
37
|
+
t = threading.Thread(target=_run, args=(loop,), daemon=True)
|
|
38
|
+
t.start()
|
|
39
|
+
_BG_LOOP = loop
|
|
40
|
+
return loop
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _run_async(coro, timeout=10):
|
|
44
|
+
"""Submit *coro* to the background loop and block until it finishes."""
|
|
45
|
+
loop = _get_bg_loop()
|
|
46
|
+
future = asyncio.run_coroutine_threadsafe(coro, loop)
|
|
47
|
+
return future.result(timeout=timeout)
|
|
48
|
+
|
|
49
|
+
|
|
9
50
|
class SocketModule:
|
|
10
51
|
"""Provides socket and TCP operations."""
|
|
11
52
|
|
|
@@ -40,214 +81,217 @@ class SocketModule:
|
|
|
40
81
|
|
|
41
82
|
|
|
42
83
|
class TCPServer:
|
|
43
|
-
"""TCP server
|
|
44
|
-
|
|
84
|
+
"""TCP server backed by ``asyncio.start_server``."""
|
|
85
|
+
|
|
45
86
|
def __init__(self, host: str, port: int, handler: Callable, backlog: int = 5):
|
|
46
87
|
self.host = host
|
|
47
88
|
self.port = port
|
|
48
89
|
self.handler = handler
|
|
49
90
|
self.backlog = backlog
|
|
50
|
-
self.socket: Optional[socket.socket] = None
|
|
51
91
|
self.running = False
|
|
52
|
-
self.
|
|
53
|
-
|
|
92
|
+
self._server: Optional[asyncio.AbstractServer] = None
|
|
93
|
+
self._loop: Optional[asyncio.AbstractEventLoop] = None
|
|
94
|
+
|
|
54
95
|
def start(self) -> None:
|
|
55
|
-
"""Start the server
|
|
96
|
+
"""Start the server on the background event loop."""
|
|
56
97
|
if self.running:
|
|
57
98
|
raise RuntimeError("Server is already running")
|
|
58
|
-
|
|
59
|
-
self.
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
99
|
+
self._loop = _get_bg_loop()
|
|
100
|
+
_run_async(self._async_start())
|
|
101
|
+
|
|
102
|
+
def stop(self) -> None:
|
|
103
|
+
"""Gracefully stop the server."""
|
|
104
|
+
if not self.running:
|
|
105
|
+
return
|
|
106
|
+
self.running = False
|
|
107
|
+
if self._server and self._loop:
|
|
108
|
+
asyncio.run_coroutine_threadsafe(self._async_stop(), self._loop).result(timeout=5)
|
|
109
|
+
|
|
110
|
+
def is_running(self) -> bool:
|
|
111
|
+
return self.running
|
|
112
|
+
|
|
113
|
+
def get_address(self) -> Dict[str, Any]:
|
|
114
|
+
return {'host': self.host, 'port': self.port, 'running': self.running}
|
|
115
|
+
|
|
116
|
+
# -- async internals ----------------------------------------------------
|
|
117
|
+
|
|
118
|
+
async def _async_start(self):
|
|
119
|
+
server = await asyncio.start_server(
|
|
120
|
+
self._async_handle,
|
|
121
|
+
self.host,
|
|
122
|
+
self.port,
|
|
123
|
+
backlog=self.backlog,
|
|
124
|
+
reuse_address=True,
|
|
125
|
+
)
|
|
126
|
+
self._server = server
|
|
63
127
|
self.running = True
|
|
128
|
+
|
|
129
|
+
async def _async_stop(self):
|
|
130
|
+
if self._server:
|
|
131
|
+
self._server.close()
|
|
132
|
+
await self._server.wait_closed()
|
|
133
|
+
self.running = False
|
|
134
|
+
|
|
135
|
+
async def _async_handle(self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter):
|
|
136
|
+
"""Handle a single incoming connection.
|
|
64
137
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
try:
|
|
73
|
-
self.socket.settimeout(1.0) # Allow checking self.running
|
|
74
|
-
client_socket, address = self.socket.accept()
|
|
75
|
-
|
|
76
|
-
# Spawn handler in new thread
|
|
77
|
-
handler_thread = threading.Thread(
|
|
78
|
-
target=self._handle_connection,
|
|
79
|
-
args=(client_socket, address),
|
|
80
|
-
daemon=True
|
|
81
|
-
)
|
|
82
|
-
handler_thread.start()
|
|
83
|
-
|
|
84
|
-
except socket.timeout:
|
|
85
|
-
continue
|
|
86
|
-
except Exception as e:
|
|
87
|
-
if self.running: # Only log if we're not shutting down
|
|
88
|
-
print(f"Server accept error: {e}")
|
|
89
|
-
break
|
|
90
|
-
|
|
91
|
-
def _handle_connection(self, client_socket: socket.socket, address: tuple):
|
|
92
|
-
"""Handle a single client connection."""
|
|
138
|
+
The user-supplied handler is synchronous (Zexus Action), so we run it
|
|
139
|
+
in a thread executor. This avoids the send/receive → _run_async
|
|
140
|
+
deadlock because the handler thread is NOT the event-loop thread.
|
|
141
|
+
"""
|
|
142
|
+
addr = writer.get_extra_info('peername') or ('unknown', 0)
|
|
143
|
+
conn = TCPConnection._from_streams(reader, writer, addr[0], addr[1])
|
|
144
|
+
loop = asyncio.get_running_loop()
|
|
93
145
|
try:
|
|
94
|
-
|
|
95
|
-
self.handler(connection)
|
|
146
|
+
await loop.run_in_executor(None, self.handler, conn)
|
|
96
147
|
except Exception as e:
|
|
97
148
|
print(f"Connection handler error: {e}")
|
|
98
149
|
finally:
|
|
150
|
+
# Ensure connection is marked closed (handler may have already closed it)
|
|
151
|
+
conn.connected = False
|
|
99
152
|
try:
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
def stop(self) -> None:
|
|
105
|
-
"""Stop the server."""
|
|
106
|
-
self.running = False
|
|
107
|
-
if self.socket:
|
|
108
|
-
try:
|
|
109
|
-
self.socket.close()
|
|
110
|
-
except:
|
|
153
|
+
if not writer.is_closing():
|
|
154
|
+
writer.close()
|
|
155
|
+
except Exception:
|
|
111
156
|
pass
|
|
112
|
-
if self.thread:
|
|
113
|
-
self.thread.join(timeout=2.0)
|
|
114
|
-
|
|
115
|
-
def is_running(self) -> bool:
|
|
116
|
-
"""Check if server is running."""
|
|
117
|
-
return self.running
|
|
118
|
-
|
|
119
|
-
def get_address(self) -> Dict[str, Any]:
|
|
120
|
-
"""Get server address info."""
|
|
121
|
-
return {
|
|
122
|
-
'host': self.host,
|
|
123
|
-
'port': self.port,
|
|
124
|
-
'running': self.running
|
|
125
|
-
}
|
|
126
157
|
|
|
127
158
|
|
|
128
159
|
class TCPConnection:
|
|
129
|
-
"""
|
|
130
|
-
|
|
160
|
+
"""TCP connection using asyncio streams.
|
|
161
|
+
|
|
162
|
+
All public methods are **synchronous** (for Zexus evaluator compat)
|
|
163
|
+
but internally schedule asyncio coroutines on the background loop.
|
|
164
|
+
"""
|
|
165
|
+
|
|
131
166
|
def __init__(self, host: str, port: int, timeout: float = 5.0):
|
|
132
|
-
"""Create a new client connection."""
|
|
167
|
+
"""Create a new client connection (blocking)."""
|
|
133
168
|
self.host = host
|
|
134
169
|
self.port = port
|
|
135
|
-
self.
|
|
136
|
-
self.
|
|
137
|
-
self.
|
|
170
|
+
self.connected = False
|
|
171
|
+
self._reader: Optional[asyncio.StreamReader] = None
|
|
172
|
+
self._writer: Optional[asyncio.StreamWriter] = None
|
|
173
|
+
self._loop = _get_bg_loop()
|
|
174
|
+
|
|
175
|
+
async def _open():
|
|
176
|
+
return await asyncio.wait_for(
|
|
177
|
+
asyncio.open_connection(host, port), timeout=timeout)
|
|
178
|
+
|
|
179
|
+
self._reader, self._writer = _run_async(_open())
|
|
138
180
|
self.connected = True
|
|
139
|
-
|
|
181
|
+
|
|
182
|
+
@classmethod
|
|
183
|
+
def _from_streams(cls, reader: asyncio.StreamReader, writer: asyncio.StreamWriter,
|
|
184
|
+
host: str, port: int) -> 'TCPConnection':
|
|
185
|
+
"""Wrap existing asyncio streams (used by TCPServer for accepted conns)."""
|
|
186
|
+
conn = cls.__new__(cls)
|
|
187
|
+
conn.host = host
|
|
188
|
+
conn.port = port
|
|
189
|
+
conn._reader = reader
|
|
190
|
+
conn._writer = writer
|
|
191
|
+
conn._loop = _get_bg_loop()
|
|
192
|
+
conn.connected = True
|
|
193
|
+
return conn
|
|
194
|
+
|
|
140
195
|
@classmethod
|
|
141
|
-
def from_socket(cls, sock: socket.socket, address: tuple):
|
|
142
|
-
"""Create
|
|
196
|
+
def from_socket(cls, sock: socket.socket, address: tuple) -> 'TCPConnection':
|
|
197
|
+
"""Create from a raw socket (backward-compat shim)."""
|
|
143
198
|
conn = cls.__new__(cls)
|
|
144
|
-
conn.socket = sock
|
|
145
199
|
conn.host = address[0]
|
|
146
200
|
conn.port = address[1]
|
|
201
|
+
conn._reader = None
|
|
202
|
+
conn._writer = None
|
|
203
|
+
conn._loop = _get_bg_loop()
|
|
147
204
|
conn.connected = True
|
|
205
|
+
conn._raw_sock = sock
|
|
206
|
+
|
|
207
|
+
async def _wrap():
|
|
208
|
+
return await asyncio.open_connection(sock=sock)
|
|
209
|
+
|
|
210
|
+
try:
|
|
211
|
+
conn._reader, conn._writer = _run_async(_wrap())
|
|
212
|
+
except Exception:
|
|
213
|
+
pass
|
|
148
214
|
return conn
|
|
149
|
-
|
|
215
|
+
|
|
216
|
+
# -- send ---------------------------------------------------------------
|
|
217
|
+
|
|
150
218
|
def send(self, data: bytes) -> int:
|
|
151
|
-
"""Send data over the connection.
|
|
152
|
-
|
|
153
|
-
Args:
|
|
154
|
-
data: Bytes to send
|
|
155
|
-
|
|
156
|
-
Returns:
|
|
157
|
-
Number of bytes sent
|
|
158
|
-
"""
|
|
159
219
|
if not self.connected:
|
|
160
220
|
raise RuntimeError("Connection is closed")
|
|
161
|
-
|
|
162
|
-
|
|
221
|
+
|
|
222
|
+
async def _send():
|
|
223
|
+
self._writer.write(data)
|
|
224
|
+
await self._writer.drain()
|
|
225
|
+
|
|
226
|
+
_run_async(_send())
|
|
227
|
+
return len(data)
|
|
228
|
+
|
|
163
229
|
def send_string(self, text: str, encoding: str = 'utf-8') -> int:
|
|
164
|
-
"""Send string over the connection.
|
|
165
|
-
|
|
166
|
-
Args:
|
|
167
|
-
text: String to send
|
|
168
|
-
encoding: Text encoding
|
|
169
|
-
|
|
170
|
-
Returns:
|
|
171
|
-
Number of bytes sent
|
|
172
|
-
"""
|
|
173
230
|
return self.send(text.encode(encoding))
|
|
174
|
-
|
|
231
|
+
|
|
232
|
+
# -- receive ------------------------------------------------------------
|
|
233
|
+
|
|
175
234
|
def receive(self, buffer_size: int = 4096) -> bytes:
|
|
176
|
-
"""Receive data from the connection.
|
|
177
|
-
|
|
178
|
-
Args:
|
|
179
|
-
buffer_size: Maximum bytes to receive
|
|
180
|
-
|
|
181
|
-
Returns:
|
|
182
|
-
Received bytes (empty if connection closed)
|
|
183
|
-
"""
|
|
184
235
|
if not self.connected:
|
|
185
236
|
raise RuntimeError("Connection is closed")
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
237
|
+
|
|
238
|
+
async def _recv():
|
|
239
|
+
try:
|
|
240
|
+
return await asyncio.wait_for(self._reader.read(buffer_size), timeout=5.0)
|
|
241
|
+
except asyncio.TimeoutError:
|
|
242
|
+
return b''
|
|
243
|
+
|
|
244
|
+
data = _run_async(_recv())
|
|
245
|
+
if not data:
|
|
246
|
+
self.connected = False
|
|
247
|
+
return data
|
|
248
|
+
|
|
195
249
|
def receive_string(self, buffer_size: int = 4096, encoding: str = 'utf-8') -> str:
|
|
196
|
-
"""Receive string from the connection.
|
|
197
|
-
|
|
198
|
-
Args:
|
|
199
|
-
buffer_size: Maximum bytes to receive
|
|
200
|
-
encoding: Text encoding
|
|
201
|
-
|
|
202
|
-
Returns:
|
|
203
|
-
Received string
|
|
204
|
-
"""
|
|
205
250
|
data = self.receive(buffer_size)
|
|
206
251
|
return data.decode(encoding) if data else ''
|
|
207
|
-
|
|
252
|
+
|
|
208
253
|
def receive_all(self, timeout: float = 5.0) -> bytes:
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
Args:
|
|
212
|
-
timeout: Maximum time to wait
|
|
213
|
-
|
|
214
|
-
Returns:
|
|
215
|
-
All received bytes
|
|
216
|
-
"""
|
|
217
|
-
chunks = []
|
|
218
|
-
start_time = time.time()
|
|
219
|
-
self.socket.settimeout(0.1) # Small timeout for checking
|
|
220
|
-
|
|
221
|
-
while time.time() - start_time < timeout:
|
|
254
|
+
async def _recv_all():
|
|
255
|
+
chunks = []
|
|
222
256
|
try:
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
return
|
|
233
|
-
|
|
257
|
+
while True:
|
|
258
|
+
chunk = await asyncio.wait_for(self._reader.read(4096), timeout=0.1)
|
|
259
|
+
if not chunk:
|
|
260
|
+
break
|
|
261
|
+
chunks.append(chunk)
|
|
262
|
+
except asyncio.TimeoutError:
|
|
263
|
+
pass
|
|
264
|
+
return b''.join(chunks)
|
|
265
|
+
|
|
266
|
+
return _run_async(_recv_all())
|
|
267
|
+
|
|
268
|
+
# -- close --------------------------------------------------------------
|
|
269
|
+
|
|
234
270
|
def close(self) -> None:
|
|
235
|
-
|
|
236
|
-
|
|
271
|
+
if not self.connected:
|
|
272
|
+
return
|
|
273
|
+
self.connected = False
|
|
274
|
+
if self._writer:
|
|
237
275
|
try:
|
|
238
|
-
|
|
239
|
-
|
|
276
|
+
async def _close():
|
|
277
|
+
if not self._writer.is_closing():
|
|
278
|
+
self._writer.close()
|
|
279
|
+
try:
|
|
280
|
+
await asyncio.wait_for(self._writer.wait_closed(), timeout=2.0)
|
|
281
|
+
except (asyncio.TimeoutError, Exception):
|
|
282
|
+
pass
|
|
283
|
+
_run_async(_close(), timeout=3)
|
|
284
|
+
except Exception:
|
|
240
285
|
pass
|
|
241
|
-
|
|
242
|
-
|
|
286
|
+
raw = getattr(self, '_raw_sock', None)
|
|
287
|
+
if raw:
|
|
288
|
+
try:
|
|
289
|
+
raw.close()
|
|
290
|
+
except Exception:
|
|
291
|
+
pass
|
|
292
|
+
|
|
243
293
|
def is_connected(self) -> bool:
|
|
244
|
-
"""Check if connection is still open."""
|
|
245
294
|
return self.connected
|
|
246
|
-
|
|
295
|
+
|
|
247
296
|
def get_address(self) -> Dict[str, Any]:
|
|
248
|
-
|
|
249
|
-
return {
|
|
250
|
-
'host': self.host,
|
|
251
|
-
'port': self.port,
|
|
252
|
-
'connected': self.connected
|
|
253
|
-
}
|
|
297
|
+
return {'host': self.host, 'port': self.port, 'connected': self.connected}
|