Fix ruff lint findings across client, protocol, and tests
Continuous Integration / lint-and-security (pull_request) Successful in 20s
Continuous Integration / tests-and-coverage (pull_request) Successful in 25s

Resolve 51 findings from the I/RUF/BLE/TRY002/S110/PLR0402 rule set:

- Sort imports and __all__ (I001, RUF022, PLR0402). The sys.path.insert
  calls in check_public_calls.py and tests/test_e2e.py still precede the
  openttd imports that depend on them.
- Replace unused unpacked values with _ (RUF059) and annotate the two
  timetable lookup tables as ClassVar (RUF012).
- Narrow the best-effort excepts in OpenTTDClient.quit and
  OpenTTDAdminClient.quit to (OSError, SocketClosed) and log at debug
  rather than swallowing silently (BLE001, S110). The test doubles now
  raise an OSError subclass so they still exercise that branch.
- Narrow the gamescript JSON fallback to json.JSONDecodeError. The broad
  catch in receive_packet keeps a noqa: it guards untrusted wire data and
  must degrade to a no-op packet instead of killing the connection.
- Use contextlib.suppress instead of try/except/pass in tests.

ruff check . is clean, 102 tests pass, coverage stays at 100%.

Co-Authored-By: Claude <[email protected]>
This commit is contained in:
2026-08-26 20:57:56 +02:00
co-authored by Claude
parent 8e352ba248
commit 25953bea06
13 changed files with 136 additions and 74 deletions
+2 -2
View File
@@ -1,4 +1,4 @@
from .client import OpenTTDAdminClient, OpenTTDClient
from .decorators import exclude_call_check
from .client import OpenTTDClient, OpenTTDAdminClient
__all__ = ['OpenTTDClient', 'OpenTTDAdminClient', 'exclude_call_check']
__all__ = ['OpenTTDAdminClient', 'OpenTTDClient', 'exclude_call_check']
+46 -17
View File
@@ -1,18 +1,42 @@
import asyncio
import logging
import uuid
import monocypher
import os
import hashlib
from openttd_protocol.wire.write import write_init, write_string, write_uint8, write_uint16, write_uint32, write_presend, SEND_TCP_MTU
import logging
import os
import uuid
from typing import ClassVar
import monocypher
from openttd_protocol.wire.exceptions import SocketClosed
from openttd_protocol.wire.read import read_uint8, read_uint16
from .protocol import (
PacketGameType, OpenTTDProtocol, PacketAdminType, OpenTTDAdminProtocol, NetworkAuthenticationMethod,
GameCommand, ModifyTimetableFlags, ModifyTimetableCtrlFlag,
OrderType, OrderStopLocation, INVALID_VEH_ORDER_ID,
write_varuint, read_varuint, write_varuint_signed, read_varuint_signed
from openttd_protocol.wire.write import (
SEND_TCP_MTU,
write_init,
write_presend,
write_string,
write_uint8,
write_uint16,
write_uint32,
)
from .decorators import exclude_call_check
from .protocol import (
INVALID_VEH_ORDER_ID,
GameCommand,
ModifyTimetableCtrlFlag,
ModifyTimetableFlags,
NetworkAuthenticationMethod,
OpenTTDAdminProtocol,
OpenTTDProtocol,
OrderStopLocation,
OrderType,
PacketAdminType,
PacketGameType,
read_varuint,
read_varuint_signed,
write_varuint,
write_varuint_signed,
)
class OpenTTDClient:
"""High-level OpenTTD client for easy integration."""
@@ -261,8 +285,9 @@ class OpenTTDClient:
try:
d = write_init(PacketGameType.ClientQuit)
await self._protocol.send_packet(write_presend(d, SEND_TCP_MTU))
except Exception:
pass
except (OSError, SocketClosed) as e:
# Best-effort courtesy packet: the socket may already be gone.
self.log.debug(f"Could not send quit packet: {e}")
self._transport.close()
self.shutdown_event.set()
@@ -386,7 +411,7 @@ class OpenTTDClient:
async def receive_ServerMapData(self, source, **kwargs): pass
async def receive_ServerConfigurationUpdate(self, source, **kwargs): pass
async def receive_ServerExternalChat(self, source, **kwargs): pass
_TIMETABLE_FIELD_BY_FLAG = {
_TIMETABLE_FIELD_BY_FLAG: ClassVar[dict[ModifyTimetableFlags, str]] = {
ModifyTimetableFlags.WaitTime: "wait_time",
ModifyTimetableFlags.TravelTime: "travel_time",
ModifyTimetableFlags.TravelSpeed: "travel_speed",
@@ -395,7 +420,10 @@ class OpenTTDClient:
ModifyTimetableFlags.SetLeaveType: "leave_type",
ModifyTimetableFlags.AssignSchedule: "assigned_schedule",
}
_TIMETABLE_BOOL_FLAGS = {ModifyTimetableFlags.SetWaitFixed, ModifyTimetableFlags.SetTravelFixed}
_TIMETABLE_BOOL_FLAGS: ClassVar[set[ModifyTimetableFlags]] = {
ModifyTimetableFlags.SetWaitFixed,
ModifyTimetableFlags.SetTravelFixed,
}
async def receive_ServerCommand(self, source, cmd, payload, **kwargs):
if cmd == GameCommand.ChangeTimetable:
@@ -512,8 +540,9 @@ class OpenTTDAdminClient:
try:
d = write_init(PacketAdminType.AdminQuit)
await self._protocol.send_packet(write_presend(d, SEND_TCP_MTU))
except Exception:
pass
except (OSError, SocketClosed) as e:
# Best-effort courtesy packet: the socket may already be gone.
self.log.debug(f"Could not send admin quit packet: {e}")
self._transport.close()
self.shutdown_event.set()
@@ -597,7 +626,7 @@ class OpenTTDAdminClient:
Raises asyncio.TimeoutError if no reply arrives within `timeout`, ValueError on an error
reply, and ConnectionError if the admin connection drops while waiting.
"""
from .protocol import AdminUpdateType, AdminUpdateFrequency
from .protocol import AdminUpdateFrequency, AdminUpdateType
if not self._gs_subscribed:
await self.update_frequency(AdminUpdateType.Gamescript, AdminUpdateFrequency.Automatic)
self._gs_subscribed = True
+10 -8
View File
@@ -1,9 +1,11 @@
import struct
import monocypher
from enum import IntEnum
from openttd_protocol.wire.tcp import TCPProtocol
from openttd_protocol.wire.read import read_uint8, read_string, read_uint16, read_uint32
import monocypher
from openttd_protocol.wire.exceptions import SocketClosed
from openttd_protocol.wire.read import read_string, read_uint8, read_uint16, read_uint32
from openttd_protocol.wire.tcp import TCPProtocol
def write_varuint(buffer, value):
"""Encode a non-negative integer using OpenTTD's UTF-8-like varuint scheme."""
@@ -232,21 +234,21 @@ class OpenTTDProtocol(TCPProtocol):
if self.handler.encryption_enabled:
if not self.handler._recv_aead:
self.handler._recv_aead = monocypher.IncrementalAuthenticatedEncryption(self.handler._session_key_recv, self.handler._encryption_nonce)
length, rest = read_uint16(data)
_, rest = read_uint16(data)
payload = self.handler._recv_aead.unlock(bytes(rest[:16]), bytes(rest[16:]))
if payload is None:
raise SocketClosed("Decryption failed")
data = memoryview(struct.pack("<H", len(payload) + 2) + payload)
return super().receive_packet(source, data)
except Exception:
except Exception: # noqa: BLE001 - untrusted wire data: any decode failure must degrade to a no-op packet rather than kill the connection
return PacketGameType.ServerUnused, {}
async def send_packet(self, data):
if self.handler.encryption_enabled:
if not self.handler._send_aead:
self.handler._send_aead = monocypher.IncrementalAuthenticatedEncryption(self.handler._session_key_send, self.handler._encryption_nonce)
length, payload = read_uint16(memoryview(data))
_, payload = read_uint16(memoryview(data))
mac, ciphertext = self.handler._send_aead.lock(payload.tobytes())
data = struct.pack("<H", 18 + len(ciphertext)) + mac + ciphertext
@@ -287,7 +289,7 @@ class OpenTTDProtocol(TCPProtocol):
@staticmethod
def receive_ServerFrame(source, data):
f, data = read_uint32(data)
max_f, data = read_uint32(data)
_, data = read_uint32(data)
token = 0
if len(data) > 0:
if len(data) >= 13:
@@ -575,7 +577,7 @@ class OpenTTDAdminProtocol(OpenTTDProtocol):
import json
try:
return {"data": json.loads(json_str)}
except Exception:
except json.JSONDecodeError:
return {"raw_data": json_str}
@staticmethod