Skip to content

Commit 7bcaa55

Browse files
authored
on_gmcp becomes public API, remove gmcp_log (jquast#127)
- .``_on_gmcp`` renamed to ``on_gmcp`` as a public API method - ``gmcp_log`` isn't practical or useful for clients, removed
1 parent fdfb491 commit 7bcaa55

8 files changed

Lines changed: 25 additions & 60 deletions

File tree

docs/conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
0, os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir))
2323
)
2424

25-
suppress_warnings = ["image.nonlocal_uri"]
25+
suppress_warnings = ["image.nonlocal_uri", "ref.class"]
2626

2727
autodoc_default_flags = [
2828
"members",

docs/history.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ History
33
3.0.1 (unreleased)
44
* new: MCCP2 and MCCP3. Both client and server ends passively support if requested, and request
55
support by --compression or deny support by --no-compression.
6+
* new: MCCP2 and MCCP3. Both client and server ends passively support if requested, and request
7+
support by --compression or deny support by --no-compression.
68

79
3.0.0
810
* change: :attr:`~telnetlib3.client_base.BaseClient.connect_minwait` default

telnetlib3/client.py

Lines changed: 6 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
__all__ = ("TelnetClient", "TelnetTerminalClient", "open_connection")
2424

2525
#: Default GMCP modules requested via ``Core.Supports.Set``.
26+
#: Sub-modules are listed explicitly because not all servers treat
27+
#: top-level subscriptions as wildcards.
2628
_DEFAULT_GMCP_MODULES = [
2729
"Char 1",
2830
"Char.Vitals 1",
@@ -31,6 +33,7 @@
3133
"Room.Info 1",
3234
"Comm 1",
3335
"Comm.Channel 1",
36+
"Group 1",
3437
]
3538

3639

@@ -70,7 +73,6 @@ def __init__(
7073
waiter_closed: Optional[asyncio.Future[None]] = None,
7174
_waiter_connected: Optional[asyncio.Future[None]] = None,
7275
gmcp_modules: Optional[List[str]] = None,
73-
gmcp_log: bool = False,
7476
) -> None:
7577
"""Initialize TelnetClient with terminal parameters."""
7678
self._compression = compression
@@ -86,7 +88,6 @@ def __init__(
8688
_waiter_connected=_waiter_connected,
8789
)
8890
self._gmcp_modules = gmcp_modules or list(_DEFAULT_GMCP_MODULES)
89-
self._gmcp_log = gmcp_log
9091
self._gmcp_hello_sent = False
9192
self._send_environ = set(send_environ or self.DEFAULT_SEND_ENVIRON)
9293
self._extra.update(
@@ -166,7 +167,7 @@ def _setup_gmcp(self) -> None:
166167
"""Wire GMCP callback and WILL-detection for Core.Hello handshake."""
167168
from telnetlib3.telopt import GMCP
168169

169-
self.writer.set_ext_callback(GMCP, self._on_gmcp)
170+
self.writer.set_ext_callback(GMCP, self.on_gmcp)
170171

171172
# Capture current handle_will (already includes CHARSET wrapper).
172173
# On reconnect, _original_handle_will was already restored in connection_made,
@@ -191,17 +192,14 @@ def _send_gmcp_hello(self) -> None:
191192
self.writer.send_gmcp("Core.Supports.Set", self._gmcp_modules)
192193
self.log.info("GMCP handshake: Core.Hello + Core.Supports.Set %s", self._gmcp_modules)
193194

194-
def _on_gmcp(self, package: str, data: Any) -> None:
195+
def on_gmcp(self, package: str, data: Any) -> None:
195196
"""Store incoming GMCP data on ``writer.ctx``, merging dict updates."""
196197
gmcp = self.writer.ctx.gmcp_data
197198
if isinstance(data, dict) and isinstance(gmcp.get(package), dict):
198199
gmcp[package].update(data)
199200
else:
200201
gmcp[package] = data
201-
if self._gmcp_log:
202-
self.log.info("GMCP: %s %r", package, data)
203-
else:
204-
self.log.debug("GMCP: %s %r", package, data)
202+
self.log.debug("GMCP: %s %r", package, data)
205203

206204
def send_ttype(self) -> str:
207205
"""Callback for responding to TTYPE requests."""
@@ -628,12 +626,10 @@ async def run_client() -> None:
628626
# flags before negotiation starts.
629627
encoding_explicit = args["encoding"] not in ("utf8", "utf-8", False)
630628
gmcp_modules: Optional[List[str]] = args.get("gmcp_modules")
631-
gmcp_log: bool = args.get("gmcp_log", False)
632629

633630
def _client_factory(**kwargs: Any) -> client_base.BaseClient:
634631
client: TelnetClient
635632
kwargs["gmcp_modules"] = gmcp_modules
636-
kwargs["gmcp_log"] = gmcp_log
637633
if sys.platform != "win32" and sys.stdin.isatty():
638634
client = TelnetTerminalClient(**kwargs)
639635
else:
@@ -940,12 +936,6 @@ def _get_argument_parser() -> argparse.ArgumentParser:
940936
'(e.g. "Char 1,Room 1,IRE.Rift 1"). '
941937
"When provided, replaces the built-in defaults.",
942938
)
943-
parser.add_argument(
944-
"--gmcp-log",
945-
action="store_true",
946-
default=False,
947-
help="log all incoming GMCP messages at INFO level " "(default: DEBUG only)",
948-
)
949939
parser.add_argument(
950940
"--typescript",
951941
default=None,
@@ -1047,7 +1037,6 @@ def _transform_args(args: argparse.Namespace) -> Dict[str, Any]:
10471037
else None
10481038
),
10491039
"compression": args.compression,
1050-
"gmcp_log": args.gmcp_log,
10511040
"typescript": args.typescript,
10521041
}
10531042

telnetlib3/client_base.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -478,9 +478,7 @@ def _mccp3_end(self) -> None:
478478
"""Stop MCCP3 compression, flush Z_FINISH."""
479479
if self._mccp3_compressor is not None:
480480
if not self.writer.is_closing():
481-
self._mccp3_orig_write(
482-
self._mccp3_compressor.flush(zlib.Z_FINISH)
483-
)
481+
self._mccp3_orig_write(self._mccp3_compressor.flush(zlib.Z_FINISH))
484482
self._mccp3_compressor = None
485483
# Restore original transport.write
486484
self.writer._transport.write = self._mccp3_orig_write # type: ignore[method-assign]

telnetlib3/server.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -217,9 +217,7 @@ def _mccp2_end(self) -> None:
217217
"""Stop MCCP2 compression, flush Z_FINISH."""
218218
if self._mccp2_compressor is not None:
219219
try:
220-
self._mccp2_orig_write(
221-
self._mccp2_compressor.flush(zlib.Z_FINISH)
222-
)
220+
self._mccp2_orig_write(self._mccp2_compressor.flush(zlib.Z_FINISH))
223221
except zlib.error as exc:
224222
logger.debug("MCCP2 Z_FINISH flush error: %s", exc)
225223
self._mccp2_compressor = None

telnetlib3/stream_writer.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3047,8 +3047,7 @@ def _handle_sb_mccp3(self, buf: collections.deque[bytes]) -> None:
30473047
"""
30483048
Handle MCCP3 subnegotiation (``IAC SB MCCP3 IAC SE``).
30493049
3050-
On server side, this signals that subsequent client→server data is
3051-
zlib-compressed.
3050+
On server side, this signals that subsequent client→server data is zlib-compressed.
30523051
30533052
:param buf: bytes following IAC SB MCCP3_COMPRESS.
30543053
"""

telnetlib3/tests/test_client_unit.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -557,15 +557,15 @@ def _make_connected_client(**kwargs):
557557
@pytest.mark.asyncio
558558
async def test_on_gmcp_stores_on_writer_ctx():
559559
client, _ = _make_connected_client()
560-
client._on_gmcp("Room.Info", {"name": "Town Square"})
560+
client.on_gmcp("Room.Info", {"name": "Town Square"})
561561
assert client.writer.ctx.gmcp_data["Room.Info"] == {"name": "Town Square"}
562562

563563

564564
@pytest.mark.asyncio
565565
async def test_on_gmcp_merges_dicts_on_writer_ctx():
566566
client, _ = _make_connected_client()
567-
client._on_gmcp("Char.Vitals", {"hp": 100, "maxhp": 100})
568-
client._on_gmcp("Char.Vitals", {"hp": 63})
567+
client.on_gmcp("Char.Vitals", {"hp": 100, "maxhp": 100})
568+
client.on_gmcp("Char.Vitals", {"hp": 63})
569569
assert client.writer.ctx.gmcp_data["Char.Vitals"] == {"hp": 63, "maxhp": 100}
570570

571571

telnetlib3/tests/test_mccp.py

Lines changed: 10 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,7 @@
99
import pytest
1010

1111
# local
12-
from telnetlib3.telopt import (
13-
DO,
14-
SB,
15-
SE,
16-
IAC,
17-
DONT,
18-
WILL,
19-
WONT,
20-
MCCP2_COMPRESS,
21-
MCCP3_COMPRESS,
22-
)
12+
from telnetlib3.telopt import DO, SB, SE, IAC, DONT, WILL, WONT, MCCP2_COMPRESS, MCCP3_COMPRESS
2313
from telnetlib3.stream_writer import TelnetWriter
2414
from telnetlib3.tests.accessories import MockProtocol, MockTransport
2515

@@ -184,9 +174,7 @@ def test_mid_chunk_split(self):
184174
compressed += compressor.flush(zlib.Z_SYNC_FLUSH)
185175

186176
chunk = IAC + SB + MCCP2_COMPRESS + IAC + SE + compressed
187-
cmd_received = _process_data_chunk(
188-
chunk, w, reader, None, lambda *a: None
189-
)
177+
cmd_received = _process_data_chunk(chunk, w, reader, None, lambda *a: None)
190178
assert cmd_received is True
191179
assert w.mccp2_active is True
192180
assert w._compressed_remainder == compressed
@@ -200,9 +188,7 @@ def test_no_remainder_without_mccp2(self):
200188
w, _t, _p = new_writer(server=False, client=True, reader=reader)
201189

202190
chunk = b"plain text data"
203-
cmd_received = _process_data_chunk(
204-
chunk, w, reader, None, lambda *a: None
205-
)
191+
cmd_received = _process_data_chunk(chunk, w, reader, None, lambda *a: None)
206192
assert cmd_received is False
207193
assert w._compressed_remainder is None
208194

@@ -249,11 +235,7 @@ def test_initial_attributes(self):
249235
assert w.mccp2_active is False
250236
assert w.mccp3_active is False
251237

252-
@pytest.mark.parametrize(
253-
"opt",
254-
[MCCP2_COMPRESS, MCCP3_COMPRESS],
255-
ids=["MCCP2", "MCCP3"],
256-
)
238+
@pytest.mark.parametrize("opt", [MCCP2_COMPRESS, MCCP3_COMPRESS], ids=["MCCP2", "MCCP3"])
257239
def test_empty_sb_allowed(self, opt):
258240
w, _t, _p = new_writer(server=False, client=True)
259241
w.pending_option[SB + opt] = True
@@ -275,7 +257,10 @@ def _make_compressed(plaintext: bytes, finish: bool = False) -> bytes:
275257
_BOUNDARY_SB = IAC + SB + MCCP2_COMPRESS + IAC + SE
276258
_BOUNDARY_FULL = _BOUNDARY_SB + _BOUNDARY_COMPRESSED
277259
_BOUNDARY_SPLITS = [
278-
1, 2, 3, 4,
260+
1,
261+
2,
262+
3,
263+
4,
279264
len(_BOUNDARY_SB) - 1,
280265
len(_BOUNDARY_SB),
281266
len(_BOUNDARY_SB) + 1,
@@ -317,11 +302,7 @@ async def test_two_chunk_delivery(self, split_at):
317302
joined = b"".join(received)
318303
assert joined == _BOUNDARY_PLAINTEXT
319304

320-
@pytest.mark.parametrize(
321-
"n_chunks",
322-
[3, 5, 10],
323-
ids=["3_chunks", "5_chunks", "10_chunks"],
324-
)
305+
@pytest.mark.parametrize("n_chunks", [3, 5, 10], ids=["3_chunks", "5_chunks", "10_chunks"])
325306
async def test_multi_chunk_delivery(self, n_chunks):
326307
"""Compressed data delivered in many small chunks."""
327308
client, received = _make_client_with_capture()
@@ -347,9 +328,7 @@ async def test_z_finish_with_trailing_plaintext(self):
347328
assert joined == plaintext + trailing
348329

349330
@pytest.mark.parametrize(
350-
"split_at",
351-
[1, 4, 8, 16],
352-
ids=["byte_1", "byte_4", "byte_8", "byte_16"],
331+
"split_at", [1, 4, 8, 16], ids=["byte_1", "byte_4", "byte_8", "byte_16"]
353332
)
354333
async def test_compressed_only_boundary(self, split_at):
355334
"""Split within compressed data only (SB already processed)."""

0 commit comments

Comments
 (0)