@ajksunkang-aios/kgraph-linux-x64 0.1.2 → 0.1.3
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/bin/kgraph-launcher +15 -3
- package/lib/kgraph/scripts/build-bundle.sh +17 -4
- package/lib/site-packages/outcome/__init__.py +20 -0
- package/lib/site-packages/outcome/_impl.py +239 -0
- package/lib/site-packages/outcome/_util.py +33 -0
- package/lib/site-packages/outcome/_version.py +7 -0
- package/lib/site-packages/outcome/py.typed +0 -0
- package/lib/site-packages/outcome-1.3.0.post0.dist-info/INSTALLER +1 -0
- package/lib/site-packages/outcome-1.3.0.post0.dist-info/LICENSE +3 -0
- package/lib/site-packages/outcome-1.3.0.post0.dist-info/LICENSE.APACHE2 +202 -0
- package/lib/site-packages/outcome-1.3.0.post0.dist-info/LICENSE.MIT +20 -0
- package/lib/site-packages/outcome-1.3.0.post0.dist-info/METADATA +63 -0
- package/lib/site-packages/outcome-1.3.0.post0.dist-info/RECORD +13 -0
- package/lib/site-packages/outcome-1.3.0.post0.dist-info/WHEEL +6 -0
- package/lib/site-packages/outcome-1.3.0.post0.dist-info/top_level.txt +1 -0
- package/lib/site-packages/sniffio/__init__.py +17 -0
- package/lib/site-packages/sniffio/_impl.py +95 -0
- package/lib/site-packages/sniffio/_tests/__init__.py +0 -0
- package/lib/site-packages/sniffio/_tests/test_sniffio.py +84 -0
- package/lib/site-packages/sniffio/_version.py +3 -0
- package/lib/site-packages/sniffio/py.typed +0 -0
- package/lib/site-packages/sniffio-1.3.1.dist-info/INSTALLER +1 -0
- package/lib/site-packages/sniffio-1.3.1.dist-info/LICENSE +3 -0
- package/lib/site-packages/sniffio-1.3.1.dist-info/LICENSE.APACHE2 +202 -0
- package/lib/site-packages/sniffio-1.3.1.dist-info/LICENSE.MIT +20 -0
- package/lib/site-packages/sniffio-1.3.1.dist-info/METADATA +104 -0
- package/lib/site-packages/sniffio-1.3.1.dist-info/RECORD +14 -0
- package/lib/site-packages/sniffio-1.3.1.dist-info/WHEEL +5 -0
- package/lib/site-packages/sniffio-1.3.1.dist-info/top_level.txt +1 -0
- package/lib/site-packages/sortedcontainers/__init__.py +74 -0
- package/lib/site-packages/sortedcontainers/sorteddict.py +812 -0
- package/lib/site-packages/sortedcontainers/sortedlist.py +2646 -0
- package/lib/site-packages/sortedcontainers/sortedset.py +733 -0
- package/lib/site-packages/sortedcontainers-2.4.0.dist-info/INSTALLER +1 -0
- package/lib/site-packages/sortedcontainers-2.4.0.dist-info/LICENSE +13 -0
- package/lib/site-packages/sortedcontainers-2.4.0.dist-info/METADATA +264 -0
- package/lib/site-packages/sortedcontainers-2.4.0.dist-info/RECORD +10 -0
- package/lib/site-packages/sortedcontainers-2.4.0.dist-info/WHEEL +6 -0
- package/lib/site-packages/sortedcontainers-2.4.0.dist-info/top_level.txt +1 -0
- package/lib/site-packages/trio/__init__.py +133 -0
- package/lib/site-packages/trio/__main__.py +3 -0
- package/lib/site-packages/trio/_abc.py +714 -0
- package/lib/site-packages/trio/_channel.py +610 -0
- package/lib/site-packages/trio/_core/__init__.py +94 -0
- package/lib/site-packages/trio/_core/_asyncgens.py +243 -0
- package/lib/site-packages/trio/_core/_concat_tb.py +26 -0
- package/lib/site-packages/trio/_core/_entry_queue.py +223 -0
- package/lib/site-packages/trio/_core/_exceptions.py +169 -0
- package/lib/site-packages/trio/_core/_generated_instrumentation.py +50 -0
- package/lib/site-packages/trio/_core/_generated_io_epoll.py +98 -0
- package/lib/site-packages/trio/_core/_generated_io_kqueue.py +153 -0
- package/lib/site-packages/trio/_core/_generated_io_windows.py +204 -0
- package/lib/site-packages/trio/_core/_generated_run.py +269 -0
- package/lib/site-packages/trio/_core/_generated_windows_ffi.py +10 -0
- package/lib/site-packages/trio/_core/_instrumentation.py +117 -0
- package/lib/site-packages/trio/_core/_io_common.py +31 -0
- package/lib/site-packages/trio/_core/_io_epoll.py +385 -0
- package/lib/site-packages/trio/_core/_io_kqueue.py +292 -0
- package/lib/site-packages/trio/_core/_io_windows.py +1036 -0
- package/lib/site-packages/trio/_core/_ki.py +271 -0
- package/lib/site-packages/trio/_core/_local.py +104 -0
- package/lib/site-packages/trio/_core/_mock_clock.py +165 -0
- package/lib/site-packages/trio/_core/_parking_lot.py +317 -0
- package/lib/site-packages/trio/_core/_run.py +3148 -0
- package/lib/site-packages/trio/_core/_run_context.py +15 -0
- package/lib/site-packages/trio/_core/_tests/__init__.py +0 -0
- package/lib/site-packages/trio/_core/_tests/test_asyncgen.py +339 -0
- package/lib/site-packages/trio/_core/_tests/test_cancelled.py +222 -0
- package/lib/site-packages/trio/_core/_tests/test_exceptiongroup_gc.py +103 -0
- package/lib/site-packages/trio/_core/_tests/test_guest_mode.py +755 -0
- package/lib/site-packages/trio/_core/_tests/test_instrumentation.py +315 -0
- package/lib/site-packages/trio/_core/_tests/test_io.py +522 -0
- package/lib/site-packages/trio/_core/_tests/test_ki.py +703 -0
- package/lib/site-packages/trio/_core/_tests/test_local.py +118 -0
- package/lib/site-packages/trio/_core/_tests/test_mock_clock.py +193 -0
- package/lib/site-packages/trio/_core/_tests/test_parking_lot.py +389 -0
- package/lib/site-packages/trio/_core/_tests/test_run.py +3024 -0
- package/lib/site-packages/trio/_core/_tests/test_thread_cache.py +227 -0
- package/lib/site-packages/trio/_core/_tests/test_tutil.py +13 -0
- package/lib/site-packages/trio/_core/_tests/test_unbounded_queue.py +154 -0
- package/lib/site-packages/trio/_core/_tests/test_windows.py +305 -0
- package/lib/site-packages/trio/_core/_tests/tutil.py +117 -0
- package/lib/site-packages/trio/_core/_tests/type_tests/nursery_start.py +79 -0
- package/lib/site-packages/trio/_core/_tests/type_tests/run.py +51 -0
- package/lib/site-packages/trio/_core/_thread_cache.py +317 -0
- package/lib/site-packages/trio/_core/_traps.py +318 -0
- package/lib/site-packages/trio/_core/_unbounded_queue.py +163 -0
- package/lib/site-packages/trio/_core/_wakeup_socketpair.py +75 -0
- package/lib/site-packages/trio/_core/_windows_cffi.py +313 -0
- package/lib/site-packages/trio/_deprecate.py +171 -0
- package/lib/site-packages/trio/_dtls.py +1380 -0
- package/lib/site-packages/trio/_file_io.py +513 -0
- package/lib/site-packages/trio/_highlevel_generic.py +125 -0
- package/lib/site-packages/trio/_highlevel_open_tcp_listeners.py +251 -0
- package/lib/site-packages/trio/_highlevel_open_tcp_stream.py +397 -0
- package/lib/site-packages/trio/_highlevel_open_unix_stream.py +65 -0
- package/lib/site-packages/trio/_highlevel_serve_listeners.py +148 -0
- package/lib/site-packages/trio/_highlevel_socket.py +423 -0
- package/lib/site-packages/trio/_highlevel_ssl_helpers.py +180 -0
- package/lib/site-packages/trio/_path.py +289 -0
- package/lib/site-packages/trio/_repl.py +159 -0
- package/lib/site-packages/trio/_signals.py +185 -0
- package/lib/site-packages/trio/_socket.py +1326 -0
- package/lib/site-packages/trio/_ssl.py +964 -0
- package/lib/site-packages/trio/_subprocess.py +1178 -0
- package/lib/site-packages/trio/_subprocess_platform/__init__.py +123 -0
- package/lib/site-packages/trio/_subprocess_platform/kqueue.py +48 -0
- package/lib/site-packages/trio/_subprocess_platform/waitid.py +113 -0
- package/lib/site-packages/trio/_subprocess_platform/windows.py +11 -0
- package/lib/site-packages/trio/_sync.py +908 -0
- package/lib/site-packages/trio/_tests/__init__.py +0 -0
- package/lib/site-packages/trio/_tests/astrill-codesigning-cert.cer +0 -0
- package/lib/site-packages/trio/_tests/check_type_completeness.py +247 -0
- package/lib/site-packages/trio/_tests/module_with_deprecations.py +22 -0
- package/lib/site-packages/trio/_tests/pytest_plugin.py +54 -0
- package/lib/site-packages/trio/_tests/test_abc.py +72 -0
- package/lib/site-packages/trio/_tests/test_channel.py +750 -0
- package/lib/site-packages/trio/_tests/test_contextvars.py +56 -0
- package/lib/site-packages/trio/_tests/test_deprecate.py +277 -0
- package/lib/site-packages/trio/_tests/test_deprecate_strict_exception_groups_false.py +64 -0
- package/lib/site-packages/trio/_tests/test_dtls.py +950 -0
- package/lib/site-packages/trio/_tests/test_exports.py +626 -0
- package/lib/site-packages/trio/_tests/test_fakenet.py +317 -0
- package/lib/site-packages/trio/_tests/test_file_io.py +269 -0
- package/lib/site-packages/trio/_tests/test_highlevel_generic.py +98 -0
- package/lib/site-packages/trio/_tests/test_highlevel_open_tcp_listeners.py +419 -0
- package/lib/site-packages/trio/_tests/test_highlevel_open_tcp_stream.py +693 -0
- package/lib/site-packages/trio/_tests/test_highlevel_open_unix_stream.py +86 -0
- package/lib/site-packages/trio/_tests/test_highlevel_serve_listeners.py +186 -0
- package/lib/site-packages/trio/_tests/test_highlevel_socket.py +336 -0
- package/lib/site-packages/trio/_tests/test_highlevel_ssl_helpers.py +169 -0
- package/lib/site-packages/trio/_tests/test_path.py +279 -0
- package/lib/site-packages/trio/_tests/test_repl.py +428 -0
- package/lib/site-packages/trio/_tests/test_scheduler_determinism.py +47 -0
- package/lib/site-packages/trio/_tests/test_signals.py +186 -0
- package/lib/site-packages/trio/_tests/test_socket.py +1253 -0
- package/lib/site-packages/trio/_tests/test_ssl.py +1371 -0
- package/lib/site-packages/trio/_tests/test_subprocess.py +767 -0
- package/lib/site-packages/trio/_tests/test_sync.py +735 -0
- package/lib/site-packages/trio/_tests/test_testing.py +682 -0
- package/lib/site-packages/trio/_tests/test_testing_raisesgroup.py +1128 -0
- package/lib/site-packages/trio/_tests/test_threads.py +1173 -0
- package/lib/site-packages/trio/_tests/test_timeouts.py +281 -0
- package/lib/site-packages/trio/_tests/test_tracing.py +88 -0
- package/lib/site-packages/trio/_tests/test_trio.py +8 -0
- package/lib/site-packages/trio/_tests/test_unix_pipes.py +288 -0
- package/lib/site-packages/trio/_tests/test_util.py +349 -0
- package/lib/site-packages/trio/_tests/test_wait_for_object.py +225 -0
- package/lib/site-packages/trio/_tests/test_windows_pipes.py +112 -0
- package/lib/site-packages/trio/_tests/tools/__init__.py +0 -0
- package/lib/site-packages/trio/_tests/tools/test_gen_exports.py +179 -0
- package/lib/site-packages/trio/_tests/tools/test_mypy_annotate.py +140 -0
- package/lib/site-packages/trio/_tests/tools/test_sync_requirements.py +80 -0
- package/lib/site-packages/trio/_tests/type_tests/check_wraps.py +9 -0
- package/lib/site-packages/trio/_tests/type_tests/open_memory_channel.py +4 -0
- package/lib/site-packages/trio/_tests/type_tests/path.py +140 -0
- package/lib/site-packages/trio/_tests/type_tests/subprocesses.py +23 -0
- package/lib/site-packages/trio/_tests/type_tests/task_status.py +29 -0
- package/lib/site-packages/trio/_threads.py +610 -0
- package/lib/site-packages/trio/_timeouts.py +197 -0
- package/lib/site-packages/trio/_tools/__init__.py +0 -0
- package/lib/site-packages/trio/_tools/gen_exports.py +401 -0
- package/lib/site-packages/trio/_tools/mypy_annotate.py +126 -0
- package/lib/site-packages/trio/_tools/sync_requirements.py +98 -0
- package/lib/site-packages/trio/_tools/windows_ffi_build.py +220 -0
- package/lib/site-packages/trio/_unix_pipes.py +197 -0
- package/lib/site-packages/trio/_util.py +385 -0
- package/lib/site-packages/trio/_version.py +3 -0
- package/lib/site-packages/trio/_wait_for_object.py +67 -0
- package/lib/site-packages/trio/_windows_pipes.py +144 -0
- package/lib/site-packages/trio/abc.py +23 -0
- package/lib/site-packages/trio/from_thread.py +13 -0
- package/lib/site-packages/trio/lowlevel.py +95 -0
- package/lib/site-packages/trio/py.typed +0 -0
- package/lib/site-packages/trio/socket.py +602 -0
- package/lib/site-packages/trio/testing/__init__.py +58 -0
- package/lib/site-packages/trio/testing/_check_streams.py +570 -0
- package/lib/site-packages/trio/testing/_checkpoints.py +69 -0
- package/lib/site-packages/trio/testing/_fake_net.py +584 -0
- package/lib/site-packages/trio/testing/_memory_streams.py +633 -0
- package/lib/site-packages/trio/testing/_network.py +36 -0
- package/lib/site-packages/trio/testing/_raises_group.py +1015 -0
- package/lib/site-packages/trio/testing/_sequencer.py +87 -0
- package/lib/site-packages/trio/testing/_trio_test.py +50 -0
- package/lib/site-packages/trio/to_thread.py +4 -0
- package/lib/site-packages/trio-0.33.0.dist-info/INSTALLER +1 -0
- package/lib/site-packages/trio-0.33.0.dist-info/METADATA +186 -0
- package/lib/site-packages/trio-0.33.0.dist-info/RECORD +156 -0
- package/lib/site-packages/trio-0.33.0.dist-info/REQUESTED +0 -0
- package/lib/site-packages/trio-0.33.0.dist-info/WHEEL +5 -0
- package/lib/site-packages/trio-0.33.0.dist-info/entry_points.txt +2 -0
- package/lib/site-packages/trio-0.33.0.dist-info/licenses/LICENSE +3 -0
- package/lib/site-packages/trio-0.33.0.dist-info/licenses/LICENSE.APACHE2 +202 -0
- package/lib/site-packages/trio-0.33.0.dist-info/licenses/LICENSE.MIT +22 -0
- package/lib/site-packages/trio-0.33.0.dist-info/top_level.txt +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,633 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import operator
|
|
4
|
+
from collections.abc import Awaitable, Callable
|
|
5
|
+
from typing import TypeAlias, TypeVar
|
|
6
|
+
|
|
7
|
+
from .. import _core, _util
|
|
8
|
+
from .._highlevel_generic import StapledStream
|
|
9
|
+
from ..abc import ReceiveStream, SendStream
|
|
10
|
+
|
|
11
|
+
AsyncHook: TypeAlias = Callable[[], Awaitable[object]]
|
|
12
|
+
# Would be nice to exclude awaitable here, but currently not possible.
|
|
13
|
+
SyncHook: TypeAlias = Callable[[], object]
|
|
14
|
+
SendStreamT = TypeVar("SendStreamT", bound=SendStream)
|
|
15
|
+
ReceiveStreamT = TypeVar("ReceiveStreamT", bound=ReceiveStream)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
################################################################
|
|
19
|
+
# In-memory streams - Unbounded buffer version
|
|
20
|
+
################################################################
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class _UnboundedByteQueue:
|
|
24
|
+
def __init__(self) -> None:
|
|
25
|
+
self._data = bytearray()
|
|
26
|
+
self._closed = False
|
|
27
|
+
self._lot = _core.ParkingLot()
|
|
28
|
+
self._fetch_lock = _util.ConflictDetector(
|
|
29
|
+
"another task is already fetching data",
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
# This object treats "close" as being like closing the send side of a
|
|
33
|
+
# channel: so after close(), calling put() raises ClosedResourceError, and
|
|
34
|
+
# calling the get() variants drains the buffer and then returns an empty
|
|
35
|
+
# bytearray.
|
|
36
|
+
def close(self) -> None:
|
|
37
|
+
self._closed = True
|
|
38
|
+
self._lot.unpark_all()
|
|
39
|
+
|
|
40
|
+
def close_and_wipe(self) -> None:
|
|
41
|
+
self._data = bytearray()
|
|
42
|
+
self.close()
|
|
43
|
+
|
|
44
|
+
def put(self, data: bytes | bytearray | memoryview) -> None:
|
|
45
|
+
if self._closed:
|
|
46
|
+
raise _core.ClosedResourceError("virtual connection closed")
|
|
47
|
+
self._data += data
|
|
48
|
+
self._lot.unpark_all()
|
|
49
|
+
|
|
50
|
+
def _check_max_bytes(self, max_bytes: int | None) -> None:
|
|
51
|
+
if max_bytes is None:
|
|
52
|
+
return
|
|
53
|
+
max_bytes = operator.index(max_bytes)
|
|
54
|
+
if max_bytes < 1:
|
|
55
|
+
raise ValueError("max_bytes must be >= 1")
|
|
56
|
+
|
|
57
|
+
def _get_impl(self, max_bytes: int | None) -> bytearray:
|
|
58
|
+
assert self._closed or self._data
|
|
59
|
+
if max_bytes is None:
|
|
60
|
+
max_bytes = len(self._data)
|
|
61
|
+
if self._data:
|
|
62
|
+
chunk = self._data[:max_bytes]
|
|
63
|
+
del self._data[:max_bytes]
|
|
64
|
+
assert chunk
|
|
65
|
+
return chunk
|
|
66
|
+
else:
|
|
67
|
+
return bytearray()
|
|
68
|
+
|
|
69
|
+
def get_nowait(self, max_bytes: int | None = None) -> bytearray:
|
|
70
|
+
with self._fetch_lock:
|
|
71
|
+
self._check_max_bytes(max_bytes)
|
|
72
|
+
if not self._closed and not self._data:
|
|
73
|
+
raise _core.WouldBlock
|
|
74
|
+
return self._get_impl(max_bytes)
|
|
75
|
+
|
|
76
|
+
async def get(self, max_bytes: int | None = None) -> bytearray:
|
|
77
|
+
with self._fetch_lock:
|
|
78
|
+
self._check_max_bytes(max_bytes)
|
|
79
|
+
if not self._closed and not self._data:
|
|
80
|
+
await self._lot.park()
|
|
81
|
+
else:
|
|
82
|
+
await _core.checkpoint()
|
|
83
|
+
return self._get_impl(max_bytes)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@_util.final
|
|
87
|
+
class MemorySendStream(SendStream):
|
|
88
|
+
"""An in-memory :class:`~trio.abc.SendStream`.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
send_all_hook: An async function, or None. Called from
|
|
92
|
+
:meth:`send_all`. Can do whatever you like.
|
|
93
|
+
wait_send_all_might_not_block_hook: An async function, or None. Called
|
|
94
|
+
from :meth:`wait_send_all_might_not_block`. Can do whatever you
|
|
95
|
+
like.
|
|
96
|
+
close_hook: A synchronous function, or None. Called from :meth:`close`
|
|
97
|
+
and :meth:`aclose`. Can do whatever you like.
|
|
98
|
+
|
|
99
|
+
.. attribute:: send_all_hook
|
|
100
|
+
wait_send_all_might_not_block_hook
|
|
101
|
+
close_hook
|
|
102
|
+
|
|
103
|
+
All of these hooks are also exposed as attributes on the object, and
|
|
104
|
+
you can change them at any time.
|
|
105
|
+
|
|
106
|
+
"""
|
|
107
|
+
|
|
108
|
+
def __init__(
|
|
109
|
+
self,
|
|
110
|
+
send_all_hook: AsyncHook | None = None,
|
|
111
|
+
wait_send_all_might_not_block_hook: AsyncHook | None = None,
|
|
112
|
+
close_hook: SyncHook | None = None,
|
|
113
|
+
) -> None:
|
|
114
|
+
self._conflict_detector = _util.ConflictDetector(
|
|
115
|
+
"another task is using this stream",
|
|
116
|
+
)
|
|
117
|
+
self._outgoing = _UnboundedByteQueue()
|
|
118
|
+
self.send_all_hook = send_all_hook
|
|
119
|
+
self.wait_send_all_might_not_block_hook = wait_send_all_might_not_block_hook
|
|
120
|
+
self.close_hook = close_hook
|
|
121
|
+
|
|
122
|
+
async def send_all(self, data: bytes | bytearray | memoryview) -> None:
|
|
123
|
+
"""Places the given data into the object's internal buffer, and then
|
|
124
|
+
calls the :attr:`send_all_hook` (if any).
|
|
125
|
+
|
|
126
|
+
"""
|
|
127
|
+
# Execute two checkpoints so we have more of a chance to detect
|
|
128
|
+
# buggy user code that calls this twice at the same time.
|
|
129
|
+
with self._conflict_detector:
|
|
130
|
+
await _core.checkpoint()
|
|
131
|
+
await _core.checkpoint()
|
|
132
|
+
self._outgoing.put(data)
|
|
133
|
+
if self.send_all_hook is not None:
|
|
134
|
+
await self.send_all_hook()
|
|
135
|
+
|
|
136
|
+
async def wait_send_all_might_not_block(self) -> None:
|
|
137
|
+
"""Calls the :attr:`wait_send_all_might_not_block_hook` (if any), and
|
|
138
|
+
then returns immediately.
|
|
139
|
+
|
|
140
|
+
"""
|
|
141
|
+
# Execute two checkpoints so that we have more of a chance to detect
|
|
142
|
+
# buggy user code that calls this twice at the same time.
|
|
143
|
+
with self._conflict_detector:
|
|
144
|
+
await _core.checkpoint()
|
|
145
|
+
await _core.checkpoint()
|
|
146
|
+
# check for being closed:
|
|
147
|
+
self._outgoing.put(b"")
|
|
148
|
+
if self.wait_send_all_might_not_block_hook is not None:
|
|
149
|
+
await self.wait_send_all_might_not_block_hook()
|
|
150
|
+
|
|
151
|
+
def close(self) -> None:
|
|
152
|
+
"""Marks this stream as closed, and then calls the :attr:`close_hook`
|
|
153
|
+
(if any).
|
|
154
|
+
|
|
155
|
+
"""
|
|
156
|
+
# XXX should this cancel any pending calls to the send_all_hook and
|
|
157
|
+
# wait_send_all_might_not_block_hook? Those are the only places where
|
|
158
|
+
# send_all and wait_send_all_might_not_block can be blocked.
|
|
159
|
+
#
|
|
160
|
+
# The way we set things up, send_all_hook is memory_stream_pump, and
|
|
161
|
+
# wait_send_all_might_not_block_hook is unset. memory_stream_pump is
|
|
162
|
+
# synchronous. So normally, send_all and wait_send_all_might_not_block
|
|
163
|
+
# cannot block at all.
|
|
164
|
+
self._outgoing.close()
|
|
165
|
+
if self.close_hook is not None:
|
|
166
|
+
self.close_hook()
|
|
167
|
+
|
|
168
|
+
async def aclose(self) -> None:
|
|
169
|
+
"""Same as :meth:`close`, but async."""
|
|
170
|
+
self.close()
|
|
171
|
+
await _core.checkpoint()
|
|
172
|
+
|
|
173
|
+
async def get_data(self, max_bytes: int | None = None) -> bytearray:
|
|
174
|
+
"""Retrieves data from the internal buffer, blocking if necessary.
|
|
175
|
+
|
|
176
|
+
Args:
|
|
177
|
+
max_bytes (int or None): The maximum amount of data to
|
|
178
|
+
retrieve. None (the default) means to retrieve all the data
|
|
179
|
+
that's present (but still blocks until at least one byte is
|
|
180
|
+
available).
|
|
181
|
+
|
|
182
|
+
Returns:
|
|
183
|
+
If this stream has been closed, an empty bytearray. Otherwise, the
|
|
184
|
+
requested data.
|
|
185
|
+
|
|
186
|
+
"""
|
|
187
|
+
return await self._outgoing.get(max_bytes)
|
|
188
|
+
|
|
189
|
+
def get_data_nowait(self, max_bytes: int | None = None) -> bytearray:
|
|
190
|
+
"""Retrieves data from the internal buffer, but doesn't block.
|
|
191
|
+
|
|
192
|
+
See :meth:`get_data` for details.
|
|
193
|
+
|
|
194
|
+
Raises:
|
|
195
|
+
trio.WouldBlock: if no data is available to retrieve.
|
|
196
|
+
|
|
197
|
+
"""
|
|
198
|
+
return self._outgoing.get_nowait(max_bytes)
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
@_util.final
|
|
202
|
+
class MemoryReceiveStream(ReceiveStream):
|
|
203
|
+
"""An in-memory :class:`~trio.abc.ReceiveStream`.
|
|
204
|
+
|
|
205
|
+
Args:
|
|
206
|
+
receive_some_hook: An async function, or None. Called from
|
|
207
|
+
:meth:`receive_some`. Can do whatever you like.
|
|
208
|
+
close_hook: A synchronous function, or None. Called from :meth:`close`
|
|
209
|
+
and :meth:`aclose`. Can do whatever you like.
|
|
210
|
+
|
|
211
|
+
.. attribute:: receive_some_hook
|
|
212
|
+
close_hook
|
|
213
|
+
|
|
214
|
+
Both hooks are also exposed as attributes on the object, and you can
|
|
215
|
+
change them at any time.
|
|
216
|
+
|
|
217
|
+
"""
|
|
218
|
+
|
|
219
|
+
def __init__(
|
|
220
|
+
self,
|
|
221
|
+
receive_some_hook: AsyncHook | None = None,
|
|
222
|
+
close_hook: SyncHook | None = None,
|
|
223
|
+
) -> None:
|
|
224
|
+
self._conflict_detector = _util.ConflictDetector(
|
|
225
|
+
"another task is using this stream",
|
|
226
|
+
)
|
|
227
|
+
self._incoming = _UnboundedByteQueue()
|
|
228
|
+
self._closed = False
|
|
229
|
+
self.receive_some_hook = receive_some_hook
|
|
230
|
+
self.close_hook = close_hook
|
|
231
|
+
|
|
232
|
+
async def receive_some(self, max_bytes: int | None = None) -> bytearray:
|
|
233
|
+
"""Calls the :attr:`receive_some_hook` (if any), and then retrieves
|
|
234
|
+
data from the internal buffer, blocking if necessary.
|
|
235
|
+
|
|
236
|
+
"""
|
|
237
|
+
# Execute two checkpoints so we have more of a chance to detect
|
|
238
|
+
# buggy user code that calls this twice at the same time.
|
|
239
|
+
with self._conflict_detector:
|
|
240
|
+
await _core.checkpoint()
|
|
241
|
+
await _core.checkpoint()
|
|
242
|
+
if self._closed:
|
|
243
|
+
raise _core.ClosedResourceError
|
|
244
|
+
if self.receive_some_hook is not None:
|
|
245
|
+
await self.receive_some_hook()
|
|
246
|
+
# self._incoming's closure state tracks whether we got an EOF.
|
|
247
|
+
# self._closed tracks whether we, ourselves, are closed.
|
|
248
|
+
# self.close() sends an EOF to wake us up and sets self._closed,
|
|
249
|
+
# so after we wake up we have to check self._closed again.
|
|
250
|
+
data = await self._incoming.get(max_bytes)
|
|
251
|
+
if self._closed:
|
|
252
|
+
raise _core.ClosedResourceError
|
|
253
|
+
return data
|
|
254
|
+
|
|
255
|
+
def close(self) -> None:
|
|
256
|
+
"""Discards any pending data from the internal buffer, and marks this
|
|
257
|
+
stream as closed.
|
|
258
|
+
|
|
259
|
+
"""
|
|
260
|
+
self._closed = True
|
|
261
|
+
self._incoming.close_and_wipe()
|
|
262
|
+
if self.close_hook is not None:
|
|
263
|
+
self.close_hook()
|
|
264
|
+
|
|
265
|
+
async def aclose(self) -> None:
|
|
266
|
+
"""Same as :meth:`close`, but async."""
|
|
267
|
+
self.close()
|
|
268
|
+
await _core.checkpoint()
|
|
269
|
+
|
|
270
|
+
def put_data(self, data: bytes | bytearray | memoryview) -> None:
|
|
271
|
+
"""Appends the given data to the internal buffer."""
|
|
272
|
+
self._incoming.put(data)
|
|
273
|
+
|
|
274
|
+
def put_eof(self) -> None:
|
|
275
|
+
"""Adds an end-of-file marker to the internal buffer."""
|
|
276
|
+
self._incoming.close()
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
# TODO: investigate why this is necessary for the docs
|
|
280
|
+
MemorySendStream.__module__ = MemorySendStream.__module__.replace(
|
|
281
|
+
"._memory_streams", ""
|
|
282
|
+
)
|
|
283
|
+
MemoryReceiveStream.__module__ = MemoryReceiveStream.__module__.replace(
|
|
284
|
+
"._memory_streams", ""
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def memory_stream_pump(
|
|
289
|
+
memory_send_stream: MemorySendStream,
|
|
290
|
+
memory_receive_stream: MemoryReceiveStream,
|
|
291
|
+
*,
|
|
292
|
+
max_bytes: int | None = None,
|
|
293
|
+
) -> bool:
|
|
294
|
+
"""Take data out of the given :class:`MemorySendStream`'s internal buffer,
|
|
295
|
+
and put it into the given :class:`MemoryReceiveStream`'s internal buffer.
|
|
296
|
+
|
|
297
|
+
Args:
|
|
298
|
+
memory_send_stream (MemorySendStream): The stream to get data from.
|
|
299
|
+
memory_receive_stream (MemoryReceiveStream): The stream to put data into.
|
|
300
|
+
max_bytes (int or None): The maximum amount of data to transfer in this
|
|
301
|
+
call, or None to transfer all available data.
|
|
302
|
+
|
|
303
|
+
Returns:
|
|
304
|
+
True if it successfully transferred some data, or False if there was no
|
|
305
|
+
data to transfer.
|
|
306
|
+
|
|
307
|
+
This is used to implement :func:`memory_stream_one_way_pair` and
|
|
308
|
+
:func:`memory_stream_pair`; see the latter's docstring for an example
|
|
309
|
+
of how you might use it yourself.
|
|
310
|
+
|
|
311
|
+
"""
|
|
312
|
+
try:
|
|
313
|
+
data = memory_send_stream.get_data_nowait(max_bytes)
|
|
314
|
+
except _core.WouldBlock:
|
|
315
|
+
return False
|
|
316
|
+
try:
|
|
317
|
+
if not data:
|
|
318
|
+
memory_receive_stream.put_eof()
|
|
319
|
+
else:
|
|
320
|
+
memory_receive_stream.put_data(data)
|
|
321
|
+
except _core.ClosedResourceError:
|
|
322
|
+
raise _core.BrokenResourceError("MemoryReceiveStream was closed") from None
|
|
323
|
+
return True
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
def memory_stream_one_way_pair() -> tuple[MemorySendStream, MemoryReceiveStream]:
|
|
327
|
+
"""Create a connected, pure-Python, unidirectional stream with infinite
|
|
328
|
+
buffering and flexible configuration options.
|
|
329
|
+
|
|
330
|
+
You can think of this as being a no-operating-system-involved
|
|
331
|
+
Trio-streamsified version of :func:`os.pipe` (except that :func:`os.pipe`
|
|
332
|
+
returns the streams in the wrong order – we follow the superior convention
|
|
333
|
+
that data flows from left to right).
|
|
334
|
+
|
|
335
|
+
Returns:
|
|
336
|
+
A tuple (:class:`MemorySendStream`, :class:`MemoryReceiveStream`), where
|
|
337
|
+
the :class:`MemorySendStream` has its hooks set up so that it calls
|
|
338
|
+
:func:`memory_stream_pump` from its
|
|
339
|
+
:attr:`~MemorySendStream.send_all_hook` and
|
|
340
|
+
:attr:`~MemorySendStream.close_hook`.
|
|
341
|
+
|
|
342
|
+
The end result is that data automatically flows from the
|
|
343
|
+
:class:`MemorySendStream` to the :class:`MemoryReceiveStream`. But you're
|
|
344
|
+
also free to rearrange things however you like. For example, you can
|
|
345
|
+
temporarily set the :attr:`~MemorySendStream.send_all_hook` to None if you
|
|
346
|
+
want to simulate a stall in data transmission. Or see
|
|
347
|
+
:func:`memory_stream_pair` for a more elaborate example.
|
|
348
|
+
|
|
349
|
+
"""
|
|
350
|
+
send_stream = MemorySendStream()
|
|
351
|
+
recv_stream = MemoryReceiveStream()
|
|
352
|
+
|
|
353
|
+
def pump_from_send_stream_to_recv_stream() -> None:
|
|
354
|
+
memory_stream_pump(send_stream, recv_stream)
|
|
355
|
+
|
|
356
|
+
# await not used
|
|
357
|
+
async def async_pump_from_send_stream_to_recv_stream() -> None: # noqa: RUF029
|
|
358
|
+
pump_from_send_stream_to_recv_stream()
|
|
359
|
+
|
|
360
|
+
send_stream.send_all_hook = async_pump_from_send_stream_to_recv_stream
|
|
361
|
+
send_stream.close_hook = pump_from_send_stream_to_recv_stream
|
|
362
|
+
return send_stream, recv_stream
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
def _make_stapled_pair(
|
|
366
|
+
one_way_pair: Callable[[], tuple[SendStreamT, ReceiveStreamT]],
|
|
367
|
+
) -> tuple[
|
|
368
|
+
StapledStream[SendStreamT, ReceiveStreamT],
|
|
369
|
+
StapledStream[SendStreamT, ReceiveStreamT],
|
|
370
|
+
]:
|
|
371
|
+
pipe1_send, pipe1_recv = one_way_pair()
|
|
372
|
+
pipe2_send, pipe2_recv = one_way_pair()
|
|
373
|
+
stream1 = StapledStream(pipe1_send, pipe2_recv)
|
|
374
|
+
stream2 = StapledStream(pipe2_send, pipe1_recv)
|
|
375
|
+
return stream1, stream2
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
def memory_stream_pair() -> tuple[
|
|
379
|
+
StapledStream[MemorySendStream, MemoryReceiveStream],
|
|
380
|
+
StapledStream[MemorySendStream, MemoryReceiveStream],
|
|
381
|
+
]:
|
|
382
|
+
"""Create a connected, pure-Python, bidirectional stream with infinite
|
|
383
|
+
buffering and flexible configuration options.
|
|
384
|
+
|
|
385
|
+
This is a convenience function that creates two one-way streams using
|
|
386
|
+
:func:`memory_stream_one_way_pair`, and then uses
|
|
387
|
+
:class:`~trio.StapledStream` to combine them into a single bidirectional
|
|
388
|
+
stream.
|
|
389
|
+
|
|
390
|
+
This is like a no-operating-system-involved, Trio-streamsified version of
|
|
391
|
+
:func:`socket.socketpair`.
|
|
392
|
+
|
|
393
|
+
Returns:
|
|
394
|
+
A pair of :class:`~trio.StapledStream` objects that are connected so
|
|
395
|
+
that data automatically flows from one to the other in both directions.
|
|
396
|
+
|
|
397
|
+
After creating a stream pair, you can send data back and forth, which is
|
|
398
|
+
enough for simple tests::
|
|
399
|
+
|
|
400
|
+
left, right = memory_stream_pair()
|
|
401
|
+
await left.send_all(b"123")
|
|
402
|
+
assert await right.receive_some() == b"123"
|
|
403
|
+
await right.send_all(b"456")
|
|
404
|
+
assert await left.receive_some() == b"456"
|
|
405
|
+
|
|
406
|
+
But if you read the docs for :class:`~trio.StapledStream` and
|
|
407
|
+
:func:`memory_stream_one_way_pair`, you'll see that all the pieces
|
|
408
|
+
involved in wiring this up are public APIs, so you can adjust to suit the
|
|
409
|
+
requirements of your tests. For example, here's how to tweak a stream so
|
|
410
|
+
that data flowing from left to right trickles in one byte at a time (but
|
|
411
|
+
data flowing from right to left proceeds at full speed)::
|
|
412
|
+
|
|
413
|
+
left, right = memory_stream_pair()
|
|
414
|
+
async def trickle():
|
|
415
|
+
# left is a StapledStream, and left.send_stream is a MemorySendStream
|
|
416
|
+
# right is a StapledStream, and right.recv_stream is a MemoryReceiveStream
|
|
417
|
+
while memory_stream_pump(left.send_stream, right.recv_stream, max_bytes=1):
|
|
418
|
+
# Pause between each byte
|
|
419
|
+
await trio.sleep(1)
|
|
420
|
+
# Normally this send_all_hook calls memory_stream_pump directly without
|
|
421
|
+
# passing in a max_bytes. We replace it with our custom version:
|
|
422
|
+
left.send_stream.send_all_hook = trickle
|
|
423
|
+
|
|
424
|
+
And here's a simple test using our modified stream objects::
|
|
425
|
+
|
|
426
|
+
async def sender():
|
|
427
|
+
await left.send_all(b"12345")
|
|
428
|
+
await left.send_eof()
|
|
429
|
+
|
|
430
|
+
async def receiver():
|
|
431
|
+
async for data in right:
|
|
432
|
+
print(data)
|
|
433
|
+
|
|
434
|
+
async with trio.open_nursery() as nursery:
|
|
435
|
+
nursery.start_soon(sender)
|
|
436
|
+
nursery.start_soon(receiver)
|
|
437
|
+
|
|
438
|
+
By default, this will print ``b"12345"`` and then immediately exit; with
|
|
439
|
+
our trickle stream it instead sleeps 1 second, then prints ``b"1"``, then
|
|
440
|
+
sleeps 1 second, then prints ``b"2"``, etc.
|
|
441
|
+
|
|
442
|
+
Pro-tip: you can insert sleep calls (like in our example above) to
|
|
443
|
+
manipulate the flow of data across tasks... and then use
|
|
444
|
+
:class:`MockClock` and its :attr:`~MockClock.autojump_threshold`
|
|
445
|
+
functionality to keep your test suite running quickly.
|
|
446
|
+
|
|
447
|
+
If you want to stress test a protocol implementation, one nice trick is to
|
|
448
|
+
use the :mod:`random` module (preferably with a fixed seed) to move random
|
|
449
|
+
numbers of bytes at a time, and insert random sleeps in between them. You
|
|
450
|
+
can also set up a custom :attr:`~MemoryReceiveStream.receive_some_hook` if
|
|
451
|
+
you want to manipulate things on the receiving side, and not just the
|
|
452
|
+
sending side.
|
|
453
|
+
|
|
454
|
+
"""
|
|
455
|
+
return _make_stapled_pair(memory_stream_one_way_pair)
|
|
456
|
+
|
|
457
|
+
|
|
458
|
+
################################################################
|
|
459
|
+
# In-memory streams - Lockstep version
|
|
460
|
+
################################################################
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
class _LockstepByteQueue:
|
|
464
|
+
def __init__(self) -> None:
|
|
465
|
+
self._data = bytearray()
|
|
466
|
+
self._sender_closed = False
|
|
467
|
+
self._receiver_closed = False
|
|
468
|
+
self._receiver_waiting = False
|
|
469
|
+
self._waiters = _core.ParkingLot()
|
|
470
|
+
self._send_conflict_detector = _util.ConflictDetector(
|
|
471
|
+
"another task is already sending",
|
|
472
|
+
)
|
|
473
|
+
self._receive_conflict_detector = _util.ConflictDetector(
|
|
474
|
+
"another task is already receiving",
|
|
475
|
+
)
|
|
476
|
+
|
|
477
|
+
def _something_happened(self) -> None:
|
|
478
|
+
self._waiters.unpark_all()
|
|
479
|
+
|
|
480
|
+
# Always wakes up when one side is closed, because everyone always reacts
|
|
481
|
+
# to that.
|
|
482
|
+
async def _wait_for(self, fn: Callable[[], bool]) -> None:
|
|
483
|
+
while True:
|
|
484
|
+
if fn():
|
|
485
|
+
break
|
|
486
|
+
if self._sender_closed or self._receiver_closed:
|
|
487
|
+
break
|
|
488
|
+
await self._waiters.park()
|
|
489
|
+
await _core.checkpoint()
|
|
490
|
+
|
|
491
|
+
def close_sender(self) -> None:
|
|
492
|
+
self._sender_closed = True
|
|
493
|
+
self._something_happened()
|
|
494
|
+
|
|
495
|
+
def close_receiver(self) -> None:
|
|
496
|
+
self._receiver_closed = True
|
|
497
|
+
self._something_happened()
|
|
498
|
+
|
|
499
|
+
async def send_all(self, data: bytes | bytearray | memoryview) -> None:
|
|
500
|
+
with self._send_conflict_detector:
|
|
501
|
+
if self._sender_closed:
|
|
502
|
+
raise _core.ClosedResourceError
|
|
503
|
+
if self._receiver_closed:
|
|
504
|
+
raise _core.BrokenResourceError
|
|
505
|
+
assert not self._data
|
|
506
|
+
self._data += data
|
|
507
|
+
self._something_happened()
|
|
508
|
+
await self._wait_for(lambda: self._data == b"")
|
|
509
|
+
if self._sender_closed:
|
|
510
|
+
raise _core.ClosedResourceError
|
|
511
|
+
if self._data and self._receiver_closed:
|
|
512
|
+
raise _core.BrokenResourceError
|
|
513
|
+
|
|
514
|
+
async def wait_send_all_might_not_block(self) -> None:
|
|
515
|
+
with self._send_conflict_detector:
|
|
516
|
+
if self._sender_closed:
|
|
517
|
+
raise _core.ClosedResourceError
|
|
518
|
+
if self._receiver_closed:
|
|
519
|
+
await _core.checkpoint()
|
|
520
|
+
return
|
|
521
|
+
await self._wait_for(lambda: self._receiver_waiting)
|
|
522
|
+
if self._sender_closed:
|
|
523
|
+
raise _core.ClosedResourceError
|
|
524
|
+
|
|
525
|
+
async def receive_some(self, max_bytes: int | None = None) -> bytes | bytearray:
|
|
526
|
+
with self._receive_conflict_detector:
|
|
527
|
+
# Argument validation
|
|
528
|
+
if max_bytes is not None:
|
|
529
|
+
max_bytes = operator.index(max_bytes)
|
|
530
|
+
if max_bytes < 1:
|
|
531
|
+
raise ValueError("max_bytes must be >= 1")
|
|
532
|
+
# State validation
|
|
533
|
+
if self._receiver_closed:
|
|
534
|
+
raise _core.ClosedResourceError
|
|
535
|
+
# Wake wait_send_all_might_not_block and wait for data
|
|
536
|
+
self._receiver_waiting = True
|
|
537
|
+
self._something_happened()
|
|
538
|
+
try:
|
|
539
|
+
await self._wait_for(lambda: self._data != b"")
|
|
540
|
+
finally:
|
|
541
|
+
self._receiver_waiting = False
|
|
542
|
+
if self._receiver_closed:
|
|
543
|
+
raise _core.ClosedResourceError
|
|
544
|
+
# Get data, possibly waking send_all
|
|
545
|
+
if self._data:
|
|
546
|
+
# Neat trick: if max_bytes is None, then obj[:max_bytes] is
|
|
547
|
+
# the same as obj[:].
|
|
548
|
+
got = self._data[:max_bytes]
|
|
549
|
+
del self._data[:max_bytes]
|
|
550
|
+
self._something_happened()
|
|
551
|
+
return got
|
|
552
|
+
else:
|
|
553
|
+
assert self._sender_closed
|
|
554
|
+
return b""
|
|
555
|
+
|
|
556
|
+
|
|
557
|
+
class _LockstepSendStream(SendStream):
|
|
558
|
+
def __init__(self, lbq: _LockstepByteQueue) -> None:
|
|
559
|
+
self._lbq = lbq
|
|
560
|
+
|
|
561
|
+
def close(self) -> None:
|
|
562
|
+
self._lbq.close_sender()
|
|
563
|
+
|
|
564
|
+
async def aclose(self) -> None:
|
|
565
|
+
self.close()
|
|
566
|
+
await _core.checkpoint()
|
|
567
|
+
|
|
568
|
+
async def send_all(self, data: bytes | bytearray | memoryview) -> None:
|
|
569
|
+
await self._lbq.send_all(data)
|
|
570
|
+
|
|
571
|
+
async def wait_send_all_might_not_block(self) -> None:
|
|
572
|
+
await self._lbq.wait_send_all_might_not_block()
|
|
573
|
+
|
|
574
|
+
|
|
575
|
+
class _LockstepReceiveStream(ReceiveStream):
|
|
576
|
+
def __init__(self, lbq: _LockstepByteQueue) -> None:
|
|
577
|
+
self._lbq = lbq
|
|
578
|
+
|
|
579
|
+
def close(self) -> None:
|
|
580
|
+
self._lbq.close_receiver()
|
|
581
|
+
|
|
582
|
+
async def aclose(self) -> None:
|
|
583
|
+
self.close()
|
|
584
|
+
await _core.checkpoint()
|
|
585
|
+
|
|
586
|
+
async def receive_some(self, max_bytes: int | None = None) -> bytes | bytearray:
|
|
587
|
+
return await self._lbq.receive_some(max_bytes)
|
|
588
|
+
|
|
589
|
+
|
|
590
|
+
def lockstep_stream_one_way_pair() -> tuple[SendStream, ReceiveStream]:
|
|
591
|
+
"""Create a connected, pure Python, unidirectional stream where data flows
|
|
592
|
+
in lockstep.
|
|
593
|
+
|
|
594
|
+
Returns:
|
|
595
|
+
A tuple
|
|
596
|
+
(:class:`~trio.abc.SendStream`, :class:`~trio.abc.ReceiveStream`).
|
|
597
|
+
|
|
598
|
+
This stream has *absolutely no* buffering. Each call to
|
|
599
|
+
:meth:`~trio.abc.SendStream.send_all` will block until all the given data
|
|
600
|
+
has been returned by a call to
|
|
601
|
+
:meth:`~trio.abc.ReceiveStream.receive_some`.
|
|
602
|
+
|
|
603
|
+
This can be useful for testing flow control mechanisms in an extreme case,
|
|
604
|
+
or for setting up "clogged" streams to use with
|
|
605
|
+
:func:`check_one_way_stream` and friends.
|
|
606
|
+
|
|
607
|
+
In addition to fulfilling the :class:`~trio.abc.SendStream` and
|
|
608
|
+
:class:`~trio.abc.ReceiveStream` interfaces, the return objects
|
|
609
|
+
also have a synchronous ``close`` method.
|
|
610
|
+
|
|
611
|
+
"""
|
|
612
|
+
|
|
613
|
+
lbq = _LockstepByteQueue()
|
|
614
|
+
return _LockstepSendStream(lbq), _LockstepReceiveStream(lbq)
|
|
615
|
+
|
|
616
|
+
|
|
617
|
+
def lockstep_stream_pair() -> tuple[
|
|
618
|
+
StapledStream[SendStream, ReceiveStream],
|
|
619
|
+
StapledStream[SendStream, ReceiveStream],
|
|
620
|
+
]:
|
|
621
|
+
"""Create a connected, pure-Python, bidirectional stream where data flows
|
|
622
|
+
in lockstep.
|
|
623
|
+
|
|
624
|
+
Returns:
|
|
625
|
+
A tuple (:class:`~trio.StapledStream`, :class:`~trio.StapledStream`).
|
|
626
|
+
|
|
627
|
+
This is a convenience function that creates two one-way streams using
|
|
628
|
+
:func:`lockstep_stream_one_way_pair`, and then uses
|
|
629
|
+
:class:`~trio.StapledStream` to combine them into a single bidirectional
|
|
630
|
+
stream.
|
|
631
|
+
|
|
632
|
+
"""
|
|
633
|
+
return _make_stapled_pair(lockstep_stream_one_way_pair)
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
from .. import socket as tsocket
|
|
2
|
+
from .._highlevel_socket import SocketListener, SocketStream
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
async def open_stream_to_socket_listener(
|
|
6
|
+
socket_listener: SocketListener,
|
|
7
|
+
) -> SocketStream:
|
|
8
|
+
"""Connect to the given :class:`~trio.SocketListener`.
|
|
9
|
+
|
|
10
|
+
This is particularly useful in tests when you want to let a server pick
|
|
11
|
+
its own port, and then connect to it::
|
|
12
|
+
|
|
13
|
+
listeners = await trio.open_tcp_listeners(0)
|
|
14
|
+
client = await trio.testing.open_stream_to_socket_listener(listeners[0])
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
socket_listener (~trio.SocketListener): The
|
|
18
|
+
:class:`~trio.SocketListener` to connect to.
|
|
19
|
+
|
|
20
|
+
Returns:
|
|
21
|
+
SocketStream: a stream connected to the given listener.
|
|
22
|
+
|
|
23
|
+
"""
|
|
24
|
+
family = socket_listener.socket.family
|
|
25
|
+
sockaddr = socket_listener.socket.getsockname()
|
|
26
|
+
if family in (tsocket.AF_INET, tsocket.AF_INET6):
|
|
27
|
+
sockaddr = list(sockaddr)
|
|
28
|
+
if sockaddr[0] == "0.0.0.0":
|
|
29
|
+
sockaddr[0] = "127.0.0.1"
|
|
30
|
+
if sockaddr[0] == "::":
|
|
31
|
+
sockaddr[0] = "::1"
|
|
32
|
+
sockaddr = tuple(sockaddr)
|
|
33
|
+
|
|
34
|
+
sock = tsocket.socket(family=family)
|
|
35
|
+
await sock.connect(sockaddr)
|
|
36
|
+
return SocketStream(sock)
|