Browse Source

set line length back to 79 instead of 125

pull/43/head
Alex Nørgaard 2 years ago
parent
commit
3a9be1410c
No known key found for this signature in database GPG Key ID: 94D54F6A3604E97
  1. 18
      a2s/__init__.py
  2. 83
      a2s/a2s_async.py
  3. 60
      a2s/a2s_sync.py
  4. 36
      a2s/byteio.py
  5. 9
      a2s/datacls.py
  6. 12
      a2s/info.py
  7. 14
      a2s/players.py
  8. 13
      a2s/rules.py
  9. 4
      pyproject.toml

18
a2s/__init__.py

@ -1,4 +1,16 @@
from a2s.exceptions import BrokenMessageError as BrokenMessageError, BufferExhaustedError as BufferExhaustedError from a2s.exceptions import (
from a2s.info import GoldSrcInfo as GoldSrcInfo, SourceInfo as SourceInfo, ainfo as ainfo, info as info BrokenMessageError as BrokenMessageError,
from a2s.players import Player as Player, aplayers as aplayers, players as players BufferExhaustedError as BufferExhaustedError,
)
from a2s.info import (
GoldSrcInfo as GoldSrcInfo,
SourceInfo as SourceInfo,
ainfo as ainfo,
info as info,
)
from a2s.players import (
Player as Player,
aplayers as aplayers,
players as players,
)
from a2s.rules import arules as arules, rules as rules from a2s.rules import arules as arules, rules as rules

83
a2s/a2s_async.py

@ -4,7 +4,18 @@ import asyncio
import io import io
import logging import logging
import time import time
from typing import TYPE_CHECKING, Dict, List, NoReturn, Optional, Tuple, Type, TypeVar, Union, overload from typing import (
TYPE_CHECKING,
Dict,
List,
NoReturn,
Optional,
Tuple,
Type,
TypeVar,
Union,
overload,
)
from a2s.a2s_fragment import A2SFragment, decode_fragment from a2s.a2s_fragment import A2SFragment, decode_fragment
from a2s.byteio import ByteReader from a2s.byteio import ByteReader
@ -30,28 +41,42 @@ T = TypeVar("T", InfoProtocol, PlayersProtocol, RulesProtocol)
@overload @overload
async def request_async( async def request_async(
address: Tuple[str, int], timeout: float, encoding: str, a2s_proto: Type[InfoProtocol] address: Tuple[str, int],
timeout: float,
encoding: str,
a2s_proto: Type[InfoProtocol],
) -> Union[SourceInfo, GoldSrcInfo]: ) -> Union[SourceInfo, GoldSrcInfo]:
... ...
@overload @overload
async def request_async( async def request_async(
address: Tuple[str, int], timeout: float, encoding: str, a2s_proto: Type[PlayersProtocol] address: Tuple[str, int],
timeout: float,
encoding: str,
a2s_proto: Type[PlayersProtocol],
) -> List[Player]: ) -> List[Player]:
... ...
@overload @overload
async def request_async( async def request_async(
address: Tuple[str, int], timeout: float, encoding: str, a2s_proto: Type[RulesProtocol] address: Tuple[str, int],
timeout: float,
encoding: str,
a2s_proto: Type[RulesProtocol],
) -> Dict[Union[str, bytes], Union[str, bytes]]: ) -> Dict[Union[str, bytes], Union[str, bytes]]:
... ...
async def request_async( async def request_async(
address: Tuple[str, int], timeout: float, encoding: str, a2s_proto: Type[T] address: Tuple[str, int], timeout: float, encoding: str, a2s_proto: Type[T]
) -> Union[SourceInfo, GoldSrcInfo, List[Player], Dict[Union[str, bytes], Union[str, bytes]]]: ) -> Union[
SourceInfo,
GoldSrcInfo,
List[Player],
Dict[Union[str, bytes], Union[str, bytes]],
]:
conn = await A2SStreamAsync.create(address, timeout) conn = await A2SStreamAsync.create(address, timeout)
response = await request_async_impl(conn, encoding, a2s_proto) response = await request_async_impl(conn, encoding, a2s_proto)
conn.close() conn.close()
@ -101,7 +126,12 @@ async def request_async_impl(
challenge: int = 0, challenge: int = 0,
retries: int = 0, retries: int = 0,
ping: Optional[float] = None, ping: Optional[float] = None,
) -> Union[SourceInfo, GoldSrcInfo, Dict[Union[str, bytes], Union[str, bytes]], List[Player]]: ) -> Union[
SourceInfo,
GoldSrcInfo,
Dict[Union[str, bytes], Union[str, bytes]],
List[Player],
]:
send_time = time.monotonic() send_time = time.monotonic()
resp_data = await conn.request(a2s_proto.serialize_request(challenge)) resp_data = await conn.request(a2s_proto.serialize_request(challenge))
recv_time = time.monotonic() recv_time = time.monotonic()
@ -114,12 +144,18 @@ async def request_async_impl(
response_type = reader.read_uint8() response_type = reader.read_uint8()
if response_type == A2S_CHALLENGE_RESPONSE: if response_type == A2S_CHALLENGE_RESPONSE:
if retries >= DEFAULT_RETRIES: if retries >= DEFAULT_RETRIES:
raise BrokenMessageError("Server keeps sending challenge responses") raise BrokenMessageError(
"Server keeps sending challenge responses"
)
challenge = reader.read_uint32() challenge = reader.read_uint32()
return await request_async_impl(conn, encoding, a2s_proto, challenge, retries + 1, ping) return await request_async_impl(
conn, encoding, a2s_proto, challenge, retries + 1, ping
)
if not a2s_proto.validate_response_type(response_type): if not a2s_proto.validate_response_type(response_type):
raise BrokenMessageError("Invalid response type: " + hex(response_type)) raise BrokenMessageError(
"Invalid response type: " + hex(response_type)
)
return a2s_proto.deserialize_response(reader, response_type, ping) return a2s_proto.deserialize_response(reader, response_type, ping)
@ -153,15 +189,23 @@ class A2SProtocol(asyncio.DatagramProtocol):
if len(self.fragment_buf) < self.fragment_buf[0].fragment_count: if len(self.fragment_buf) < self.fragment_buf[0].fragment_count:
return # Wait for more packets to arrive return # Wait for more packets to arrive
self.fragment_buf.sort(key=lambda f: f.fragment_id) self.fragment_buf.sort(key=lambda f: f.fragment_id)
reassembled = b"".join(fragment.payload for fragment in self.fragment_buf) reassembled = b"".join(
fragment.payload for fragment in self.fragment_buf
)
# Sometimes there's an additional header present # Sometimes there's an additional header present
if reassembled.startswith(b"\xFF\xFF\xFF\xFF"): if reassembled.startswith(b"\xFF\xFF\xFF\xFF"):
reassembled = reassembled[4:] reassembled = reassembled[4:]
logger.debug("Received %s part packet with content: %r", len(self.fragment_buf), reassembled) logger.debug(
"Received %s part packet with content: %r",
len(self.fragment_buf),
reassembled,
)
self.recv_queue.put_nowait(reassembled) self.recv_queue.put_nowait(reassembled)
self.fragment_buf = [] self.fragment_buf = []
else: else:
self.error = BrokenMessageError("Invalid packet header: " + repr(header)) self.error = BrokenMessageError(
"Invalid packet header: " + repr(header)
)
self.error_event.set() self.error_event.set()
def error_received(self, exc: Exception) -> None: def error_received(self, exc: Exception) -> None:
@ -183,7 +227,12 @@ class A2SStreamAsync:
"timeout", "timeout",
) )
def __init__(self, transport: asyncio.DatagramTransport, protocol: A2SProtocol, timeout: float) -> None: def __init__(
self,
transport: asyncio.DatagramTransport,
protocol: A2SProtocol,
timeout: float,
) -> None:
self.transport: asyncio.DatagramTransport = transport self.transport: asyncio.DatagramTransport = transport
self.protocol: A2SProtocol = protocol self.protocol: A2SProtocol = protocol
self.timeout: float = timeout self.timeout: float = timeout
@ -194,7 +243,9 @@ class A2SStreamAsync:
@classmethod @classmethod
async def create(cls, address: Tuple[str, int], timeout: float) -> Self: async def create(cls, address: Tuple[str, int], timeout: float) -> Self:
loop = asyncio.get_running_loop() loop = asyncio.get_running_loop()
transport, protocol = await loop.create_datagram_endpoint(lambda: A2SProtocol(), remote_addr=address) transport, protocol = await loop.create_datagram_endpoint(
lambda: A2SProtocol(), remote_addr=address
)
return cls(transport, protocol, timeout) return cls(transport, protocol, timeout)
def send(self, payload: bytes) -> None: def send(self, payload: bytes) -> None:
@ -206,7 +257,9 @@ class A2SStreamAsync:
queue_task = asyncio.create_task(self.protocol.recv_queue.get()) queue_task = asyncio.create_task(self.protocol.recv_queue.get())
error_task = asyncio.create_task(self.protocol.error_event.wait()) error_task = asyncio.create_task(self.protocol.error_event.wait())
done, pending = await asyncio.wait( done, pending = await asyncio.wait(
{queue_task, error_task}, timeout=self.timeout, return_when=asyncio.FIRST_COMPLETED {queue_task, error_task},
timeout=self.timeout,
return_when=asyncio.FIRST_COMPLETED,
) )
for task in pending: for task in pending:

60
a2s/a2s_sync.py

@ -27,26 +27,42 @@ T = TypeVar("T", InfoProtocol, RulesProtocol, PlayersProtocol)
@overload @overload
def request_sync( def request_sync(
address: Tuple[str, int], timeout: float, encoding: str, a2s_proto: Type[InfoProtocol] address: Tuple[str, int],
timeout: float,
encoding: str,
a2s_proto: Type[InfoProtocol],
) -> Union[SourceInfo, GoldSrcInfo]: ) -> Union[SourceInfo, GoldSrcInfo]:
... ...
@overload @overload
def request_sync(address: Tuple[str, int], timeout: float, encoding: str, a2s_proto: Type[PlayersProtocol]) -> List[Player]: def request_sync(
address: Tuple[str, int],
timeout: float,
encoding: str,
a2s_proto: Type[PlayersProtocol],
) -> List[Player]:
... ...
@overload @overload
def request_sync( def request_sync(
address: Tuple[str, int], timeout: float, encoding: str, a2s_proto: Type[RulesProtocol] address: Tuple[str, int],
timeout: float,
encoding: str,
a2s_proto: Type[RulesProtocol],
) -> Dict[Union[str, bytes], Union[str, bytes]]: ) -> Dict[Union[str, bytes], Union[str, bytes]]:
... ...
def request_sync( def request_sync(
address: Tuple[str, int], timeout: float, encoding: str, a2s_proto: Type[T] address: Tuple[str, int], timeout: float, encoding: str, a2s_proto: Type[T]
) -> Union[List[Player], GoldSrcInfo, SourceInfo, Dict[Union[str, bytes], Union[str, bytes]]]: ) -> Union[
List[Player],
GoldSrcInfo,
SourceInfo,
Dict[Union[str, bytes], Union[str, bytes]],
]:
conn = A2SStream(address, timeout) conn = A2SStream(address, timeout)
response = request_sync_impl(conn, encoding, a2s_proto) response = request_sync_impl(conn, encoding, a2s_proto)
conn.close() conn.close()
@ -90,8 +106,18 @@ def request_sync_impl(
def request_sync_impl( def request_sync_impl(
conn: A2SStream, encoding: str, a2s_proto: Type[T], challenge: int = 0, retries: int = 0, ping: Optional[float] = None conn: A2SStream,
) -> Union[SourceInfo, GoldSrcInfo, List[Player], Dict[Union[str, bytes], Union[str, bytes]]]: encoding: str,
a2s_proto: Type[T],
challenge: int = 0,
retries: int = 0,
ping: Optional[float] = None,
) -> Union[
SourceInfo,
GoldSrcInfo,
List[Player],
Dict[Union[str, bytes], Union[str, bytes]],
]:
send_time = time.monotonic() send_time = time.monotonic()
resp_data = conn.request(a2s_proto.serialize_request(challenge)) resp_data = conn.request(a2s_proto.serialize_request(challenge))
recv_time = time.monotonic() recv_time = time.monotonic()
@ -104,12 +130,18 @@ def request_sync_impl(
response_type = reader.read_uint8() response_type = reader.read_uint8()
if response_type == A2S_CHALLENGE_RESPONSE: if response_type == A2S_CHALLENGE_RESPONSE:
if retries >= DEFAULT_RETRIES: if retries >= DEFAULT_RETRIES:
raise BrokenMessageError("Server keeps sending challenge responses") raise BrokenMessageError(
"Server keeps sending challenge responses"
)
challenge = reader.read_uint32() challenge = reader.read_uint32()
return request_sync_impl(conn, encoding, a2s_proto, challenge, retries + 1, ping) return request_sync_impl(
conn, encoding, a2s_proto, challenge, retries + 1, ping
)
if not a2s_proto.validate_response_type(response_type): if not a2s_proto.validate_response_type(response_type):
raise BrokenMessageError("Invalid response type: " + hex(response_type)) raise BrokenMessageError(
"Invalid response type: " + hex(response_type)
)
return a2s_proto.deserialize_response(reader, response_type, ping) return a2s_proto.deserialize_response(reader, response_type, ping)
@ -122,7 +154,9 @@ class A2SStream:
def __init__(self, address: Tuple[str, int], timeout: float) -> None: def __init__(self, address: Tuple[str, int], timeout: float) -> None:
self.address: Tuple[str, int] = address self.address: Tuple[str, int] = address
self._socket: socket.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) self._socket: socket.socket = socket.socket(
socket.AF_INET, socket.SOCK_DGRAM
)
self._socket.settimeout(timeout) self._socket.settimeout(timeout)
def __del__(self) -> None: def __del__(self) -> None:
@ -150,7 +184,11 @@ class A2SStream:
# Sometimes there's an additional header present # Sometimes there's an additional header present
if reassembled.startswith(b"\xFF\xFF\xFF\xFF"): if reassembled.startswith(b"\xFF\xFF\xFF\xFF"):
reassembled = reassembled[4:] reassembled = reassembled[4:]
logger.debug("Received %s part packet with content: %r", len(fragments), reassembled) logger.debug(
"Received %s part packet with content: %r",
len(fragments),
reassembled,
)
return reassembled return reassembled
else: else:
raise BrokenMessageError("Invalid packet header: " + repr(header)) raise BrokenMessageError("Invalid packet header: " + repr(header))

36
a2s/byteio.py

@ -10,7 +10,27 @@ if TYPE_CHECKING:
from typing_extensions import Literal from typing_extensions import Literal
STRUCT_OPTIONS = Literal[ STRUCT_OPTIONS = Literal[
"x", "c", "b", "B", "?", "h", "H", "i", "I", "l", "L", "q", "Q", "n", "N", "e", "f", "d", "s", "p", "P" "x",
"c",
"b",
"B",
"?",
"h",
"H",
"i",
"I",
"l",
"L",
"q",
"Q",
"n",
"N",
"e",
"f",
"d",
"s",
"p",
"P",
] ]
@ -21,7 +41,12 @@ class ByteReader:
"encoding", "encoding",
) )
def __init__(self, stream: io.BytesIO, endian: str = "=", encoding: Optional[str] = None) -> None: def __init__(
self,
stream: io.BytesIO,
endian: str = "=",
encoding: Optional[str] = None,
) -> None:
self.stream: io.BytesIO = stream self.stream: io.BytesIO = stream
self.endian: str = endian self.endian: str = endian
self.encoding: Optional[str] = encoding self.encoding: Optional[str] = encoding
@ -111,7 +136,12 @@ class ByteWriter:
"encoding", "encoding",
) )
def __init__(self, stream: io.BytesIO, endian: str = "=", encoding: Optional[str] = None) -> None: def __init__(
self,
stream: io.BytesIO,
endian: str = "=",
encoding: Optional[str] = None,
) -> None:
self.stream: io.BytesIO = stream self.stream: io.BytesIO = stream
self.endian: str = endian self.endian: str = endian
self.encoding: Optional[str] = encoding self.encoding: Optional[str] = encoding

9
a2s/datacls.py

@ -29,11 +29,16 @@ class DataclsBase:
yield (name, getattr(self, name)) yield (name, getattr(self, name))
def __repr__(self) -> str: def __repr__(self) -> str:
return "{}({})".format(self.__class__.__name__, ", ".join(name + "=" + repr(value) for name, value in self)) return "{}({})".format(
self.__class__.__name__,
", ".join(name + "=" + repr(value) for name, value in self),
)
class DataclsMeta(type): class DataclsMeta(type):
def __new__(cls, name: str, bases: Tuple[type, ...], prop: Dict[str, Any]) -> Self: def __new__(
cls, name: str, bases: Tuple[type, ...], prop: Dict[str, Any]
) -> Self:
values: OrderedDict[str, Any] = OrderedDict() values: OrderedDict[str, Any] = OrderedDict()
for member_name in prop["__annotations__"].keys(): for member_name in prop["__annotations__"].keys():
# Check if member has a default value set as class variable # Check if member has a default value set as class variable

12
a2s/info.py

@ -182,13 +182,17 @@ class GoldSrcInfo(metaclass=DataclsMeta):
def info( def info(
address: Tuple[str, int], timeout: float = DEFAULT_TIMEOUT, encoding: str = DEFAULT_ENCODING address: Tuple[str, int],
timeout: float = DEFAULT_TIMEOUT,
encoding: str = DEFAULT_ENCODING,
) -> Union[SourceInfo, GoldSrcInfo]: ) -> Union[SourceInfo, GoldSrcInfo]:
return request_sync(address, timeout, encoding, InfoProtocol) return request_sync(address, timeout, encoding, InfoProtocol)
async def ainfo( async def ainfo(
address: Tuple[str, int], timeout: float = DEFAULT_TIMEOUT, encoding: str = DEFAULT_ENCODING address: Tuple[str, int],
timeout: float = DEFAULT_TIMEOUT,
encoding: str = DEFAULT_ENCODING,
) -> Union[SourceInfo, GoldSrcInfo]: ) -> Union[SourceInfo, GoldSrcInfo]:
return await request_async(address, timeout, encoding, InfoProtocol) return await request_async(address, timeout, encoding, InfoProtocol)
@ -201,7 +205,9 @@ class InfoProtocol:
@staticmethod @staticmethod
def serialize_request(challenge: int) -> bytes: def serialize_request(challenge: int) -> bytes:
if challenge: if challenge:
return b"\x54Source Engine Query\0" + challenge.to_bytes(4, "little") return b"\x54Source Engine Query\0" + challenge.to_bytes(
4, "little"
)
else: else:
return b"\x54Source Engine Query\0" return b"\x54Source Engine Query\0"

14
a2s/players.py

@ -23,12 +23,18 @@ class Player(metaclass=DataclsMeta):
"""Time the player has been connected to the server""" """Time the player has been connected to the server"""
def players(address: Tuple[str, int], timeout: float = DEFAULT_TIMEOUT, encoding: str = DEFAULT_ENCODING) -> List[Player]: def players(
address: Tuple[str, int],
timeout: float = DEFAULT_TIMEOUT,
encoding: str = DEFAULT_ENCODING,
) -> List[Player]:
return request_sync(address, timeout, encoding, PlayersProtocol) return request_sync(address, timeout, encoding, PlayersProtocol)
async def aplayers( async def aplayers(
address: Tuple[str, int], timeout: float = DEFAULT_TIMEOUT, encoding: str = DEFAULT_ENCODING address: Tuple[str, int],
timeout: float = DEFAULT_TIMEOUT,
encoding: str = DEFAULT_ENCODING,
) -> List[Player]: ) -> List[Player]:
return await request_async(address, timeout, encoding, PlayersProtocol) return await request_async(address, timeout, encoding, PlayersProtocol)
@ -43,7 +49,9 @@ class PlayersProtocol:
return b"\x55" + challenge.to_bytes(4, "little") return b"\x55" + challenge.to_bytes(4, "little")
@staticmethod @staticmethod
def deserialize_response(reader: ByteReader, response_type: int, ping: Optional[float]) -> List[Player]: def deserialize_response(
reader: ByteReader, response_type: int, ping: Optional[float]
) -> List[Player]:
player_count = reader.read_uint8() player_count = reader.read_uint8()
resp = [ resp = [
Player( Player(

13
a2s/rules.py

@ -9,13 +9,17 @@ A2S_RULES_RESPONSE = 0x45
def rules( def rules(
address: Tuple[str, int], timeout: float = DEFAULT_TIMEOUT, encoding: str = DEFAULT_ENCODING address: Tuple[str, int],
timeout: float = DEFAULT_TIMEOUT,
encoding: str = DEFAULT_ENCODING,
) -> Dict[Union[str, bytes], Union[str, bytes]]: ) -> Dict[Union[str, bytes], Union[str, bytes]]:
return request_sync(address, timeout, encoding, RulesProtocol) return request_sync(address, timeout, encoding, RulesProtocol)
async def arules( async def arules(
address: Tuple[str, int], timeout: float = DEFAULT_TIMEOUT, encoding: str = DEFAULT_ENCODING address: Tuple[str, int],
timeout: float = DEFAULT_TIMEOUT,
encoding: str = DEFAULT_ENCODING,
) -> Dict[Union[str, bytes], Union[str, bytes]]: ) -> Dict[Union[str, bytes], Union[str, bytes]]:
return await request_async(address, timeout, encoding, RulesProtocol) return await request_async(address, timeout, encoding, RulesProtocol)
@ -35,5 +39,8 @@ class RulesProtocol:
) -> Dict[Union[str, bytes], Union[str, bytes]]: ) -> Dict[Union[str, bytes], Union[str, bytes]]:
rule_count = reader.read_int16() rule_count = reader.read_int16()
# Have to use tuples to preserve evaluation order # Have to use tuples to preserve evaluation order
resp = dict((reader.read_cstring(), reader.read_cstring()) for _ in range(rule_count)) resp = dict(
(reader.read_cstring(), reader.read_cstring())
for _ in range(rule_count)
)
return resp return resp

4
pyproject.toml

@ -1,12 +1,12 @@
[tool.black] [tool.black]
line-length = 125 line-length = 79
target-version = ["py37"] target-version = ["py37"]
[tool.isort] [tool.isort]
profile = "black" profile = "black"
combine_as_imports = true combine_as_imports = true
combine_star = true combine_star = true
line_length = 125 line_length = 79
[tool.pyright] [tool.pyright]
include = ["a2s/**/*.py", "a2s/**/*.pyi"] include = ["a2s/**/*.py", "a2s/**/*.pyi"]

Loading…
Cancel
Save