Skip to content

Commit

Permalink
Enable pycodestyle ruff rules
Browse files Browse the repository at this point in the history
  • Loading branch information
ItsDrike committed Aug 11, 2023
1 parent 71c88ee commit dd63794
Show file tree
Hide file tree
Showing 33 changed files with 324 additions and 75 deletions.
1 change: 1 addition & 0 deletions changes/179.docs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Enforce presence of docstrings everywhere with pydocstyle. This also adds docstring to all functions and classes that didn't already have one. Minor improvements for consistency were also made to some existing docstrings.
3 changes: 1 addition & 2 deletions docs/conf.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
"""
Configuration file for the Sphinx documentation builder.
"""Configuration file for the Sphinx documentation builder.
For the full list of built-in configuration values, see the documentation:
https://www.sphinx-doc.org/en/master/usage/configuration.html
Expand Down
6 changes: 6 additions & 0 deletions mcproto/auth/account.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@


class MismatchedAccountInfoError(Exception):
"""Exception raised when info stored in the account instance doesn't match one from API."""

def __init__(self, mismatched_variable: str, current: object, expected: object) -> None:
self.missmatched_variable = mismatched_variable
self.current = current
Expand All @@ -28,13 +30,17 @@ def __repr__(self) -> str:


class InvalidAccountAccessTokenError(Exception):
"""Exception raised when the access token of the account was reported as invalid."""

def __init__(self, access_token: str, status_error: httpx.HTTPStatusError) -> None:
self.access_token = access_token
self.status_error = status_error
super().__init__("The account access token used is not valid (key expired?)")


class Account:
"""Base class for an authenticated Minecraft account."""

__slots__ = ("username", "uuid", "access_token")

def __init__(self, username: str, uuid: McUUID, access_token: str) -> None:
Expand Down
18 changes: 18 additions & 0 deletions mcproto/auth/microsoft/oauth.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@


class MicrosoftOauthResponseErrorType(str, Enum):
"""Enum for various different kinds of exceptions that the Microsoft OAuth2 API can report."""

AUTHORIZATION_PENDING = "The user hasn't finished authenticating, but hasn't canceled the flow."
AUTHORIZATION_DECLINED = "The end user denied the authorization request."
BAD_VERIFICATION_CODE = "The device_code sent to the /token endpoint wasn't recognized."
Expand All @@ -33,6 +35,7 @@ class MicrosoftOauthResponseErrorType(str, Enum):

@classmethod
def from_status_error(cls, error: str) -> Self:
"""Determine the error kind based on the error data."""
if error == "expired_token":
return cls.EXPIRED_TOKEN
if error == "authorization_pending":
Expand All @@ -47,6 +50,8 @@ def from_status_error(cls, error: str) -> Self:


class MicrosoftOauthResponseError(Exception):
"""Exception raised on a failure from the Microsoft OAuth2 API."""

def __init__(self, exc: httpx.HTTPStatusError):
self.status_error = exc

Expand All @@ -66,6 +71,13 @@ def __repr__(self) -> str:


class MicrosoftOauthRequestData(TypedDict):
"""Data obtained from Microsoft OAuth2 API after making a new authentication request.
This data specifies where (URL) we can check with the Microsoft OAuth2 servers for a client
confirmation of this authentication request, how often we should check with this server, and
when this request expires.
"""

user_code: str
device_code: str
verification_url: str
Expand All @@ -75,6 +87,12 @@ class MicrosoftOauthRequestData(TypedDict):


class MicrosoftOauthResponseData(TypedDict):
"""Data obtained from Microsoft OAuth2 API after a successful authentication.
This data contains the access and refresh tokens, giving us the requested account access
and the expiry information.
"""

token_type: str
scope: str
expires_in: int
Expand Down
8 changes: 8 additions & 0 deletions mcproto/auth/microsoft/xbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@


class XSTSErrorType(str, Enum):
"""Enum for various different kinds of exceptions that the Xbox Secure Token Server (XSTS) API can report."""

NO_XBOX_ACCOUNT = (
"The account doesn't have an Xbox account. Once they sign up for one (or login through minecraft.net to create"
" one) then they can proceed with the login. This shouldn't happen with accounts that have purchased Minecraft"
Expand All @@ -33,6 +35,7 @@ class XSTSErrorType(str, Enum):

@classmethod
def from_status_error(cls, xerr_no: int) -> Self:
"""Determine the error kind based on the error data."""
if xerr_no == 2148916233:
return cls.NO_XBOX_ACCOUNT
if xerr_no == 2148916235:
Expand All @@ -46,6 +49,8 @@ def from_status_error(cls, xerr_no: int) -> Self:


class XSTSRequestError(Exception):
"""Exception raised on a failure from the Xbox Secure Token Server (XSTS) API."""

def __init__(self, exc: httpx.HTTPStatusError):
self.status_error = exc

Expand All @@ -60,6 +65,7 @@ def __init__(self, exc: httpx.HTTPStatusError):

@property
def msg(self) -> str:
"""Produce a message for this error."""
msg_parts = []
if self.err_type is not XSTSErrorType.UNKNOWN:
msg_parts.append(f"{self.err_type.name}: {self.err_type.value!r}")
Expand All @@ -76,6 +82,8 @@ def __repr__(self) -> str:


class XboxData(NamedTuple):
"""Xbox authentication data."""

user_hash: str
xsts_token: str

Expand Down
8 changes: 8 additions & 0 deletions mcproto/auth/msa.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,22 @@


class ServicesAPIErrorType(str, Enum):
"""Enum for various different kinds of exceptions that the Minecraft services API can report."""

INVALID_REGISTRATION = "Invalid app registration, see https://aka.ms/AppRegInfo for more information"
UNKNOWN = "This is an unknown error."

@classmethod
def from_status_error(cls, code: int, err_msg: str | None) -> Self:
"""Determine the error kind based on the error data."""
if code == 401 and err_msg == "Invalid app registration, see https://aka.ms/AppRegInfo for more information":
return cls.INVALID_REGISTRATION
return cls.UNKNOWN


class ServicesAPIError(Exception):
"""Exception raised on a failure from the Minecraft services API."""

def __init__(self, exc: httpx.HTTPStatusError):
self.status_error = exc
self.code = exc.response.status_code
Expand All @@ -42,6 +47,7 @@ def __init__(self, exc: httpx.HTTPStatusError):

@property
def msg(self) -> str:
"""Produce a message for this error."""
msg_parts = []
msg_parts.append(f"HTTP {self.code} from {self.url}:")
msg_parts.append(f"type={self.err_type.name!r}")
Expand All @@ -58,6 +64,8 @@ def __repr__(self) -> str:


class MSAAccount(Account):
"""Minecraft account logged into using Microsoft OAUth2 auth system."""

__slots__ = ()

@staticmethod
Expand Down
16 changes: 15 additions & 1 deletion mcproto/auth/yggdrasil.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@


class AuthServerApiErrorType(str, Enum):
"""Enum for various different kinds of exceptions that the authserver API can report."""

MICROSOFT_MIGRATED = "This Minecraft account was migrated, use Microsoft OAuth2 login instaed."
FORBIDDEN = "An attempt to sign in using empty or insufficiently short credentials."
INVALID_CREDENTIALS = (
Expand All @@ -42,6 +44,7 @@ class AuthServerApiErrorType(str, Enum):

@classmethod
def from_status_error(cls, code: int, short_msg: str, full_msg: str, cause_msg: str | None) -> Self:
"""Determine the error kind based on the error data."""
if code == 410:
return cls.MICROSOFT_MIGRATED
if code == 403:
Expand All @@ -60,6 +63,8 @@ def from_status_error(cls, code: int, short_msg: str, full_msg: str, cause_msg:


class AuthServerApiError(Exception):
"""Exception raised on a failure from the authserver API."""

def __init__(self, exc: httpx.HTTPStatusError):
self.status_error = exc
self.code = exc.response.status_code
Expand All @@ -77,6 +82,7 @@ def __init__(self, exc: httpx.HTTPStatusError):

@property
def msg(self) -> str:
"""Produce a message for this error."""
msg_parts = []
msg_parts.append(f"HTTP {self.code} from {self.url}:")
msg_parts.append(f"type={self.err_type.name!r}")
Expand All @@ -95,6 +101,8 @@ def __repr__(self) -> str:


class YggdrasilAccount(Account):
"""Minecraft account logged into using Yggdrasil (legacy/unmigrated) auth system."""

__slots__ = ("client_token",)

def __init__(self, username: str, uuid: McUUID, access_token: str, client_token: str | None) -> None:
Expand All @@ -105,6 +113,12 @@ def __init__(self, username: str, uuid: McUUID, access_token: str, client_token:
self.client_token = client_token

async def refresh(self, client: httpx.AsyncClient) -> None:
"""Refresh the Yggdrasil access token.
This method can be called when the access token expires, to obtain a new one without
having to go through a complete re-login. This can happen after some time period, or
for example when someone else logs in to this minecraft account elsewhere.
"""
payload = {
"accessToken": self.access_token,
"clientToken": self.client_token,
Expand Down Expand Up @@ -143,7 +157,7 @@ async def refresh(self, client: httpx.AsyncClient) -> None:
self.access_token = data["accessToken"]

async def validate(self, client: httpx.AsyncClient) -> bool:
"""Checks if the access token is (still) usable for authentication with a Minecraft server.
"""Check if the access token is (still) usable for authentication with a Minecraft server.
If this method fails, the stored access token is no longer usable for for authentcation
with a Minecraft server, but should still be good enough for :meth:`refresh`.
Expand Down
2 changes: 2 additions & 0 deletions mcproto/buffer.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@


class Buffer(BaseSyncWriter, BaseSyncReader, bytearray):
"""In-memory bytearray-like buffer supporting the common read/write operations."""

__slots__ = ("pos",)

def __init__(self, *args, **kwargs):
Expand Down
3 changes: 1 addition & 2 deletions mcproto/encryption.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@


def generate_shared_secret() -> bytes: # pragma: no cover
"""Generate a random shared secret for client
"""Generate a random shared secret for client.
This secret will be sent to the server in :class:`~mcproto.packets.login.login.LoginEncryptionResponse` packet,
and used to encrypt all future communication afterwards.
Expand Down Expand Up @@ -43,7 +43,6 @@ def encrypt_token_and_secret(
:param shared_secret: The generated shared secret
:return: A tuple containing (encrypted token, encrypted secret)
"""

# Ensure both the `shared_secret` and `verification_token` are instances
# of the bytes class, not any subclass. This is needed since the cryptography
# library calls some C code in the back, which relies on this being bytes. If
Expand Down
4 changes: 4 additions & 0 deletions mcproto/multiplayer.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,15 @@


class UserJoinRequestErrorKind(str, Enum):
"""Enum for various different kinds of exceptions that can occur during :func:`join_request`."""

BANNED_FROM_MULTIPLAYER = "User with has been banned from multiplayer."
XBOX_MULTIPLAYER_DISABLED = "User's Xbox profile has multiplayer disabled."
UNKNOWN = "This is an unknown error."

@classmethod
def from_status_error(cls, code: int, err_msg: str | None) -> Self:
"""Determine the error kind based on the status code and error message."""
if code == 403:
if err_msg == "InsufficientPrivilegesException":
return cls.XBOX_MULTIPLAYER_DISABLED
Expand Down Expand Up @@ -61,6 +64,7 @@ def __init__(self, exc: httpx.HTTPStatusError):

@property
def msg(self) -> str:
"""Produce a message for this error."""
msg_parts = []
msg_parts.append(f"HTTP {self.code} from {self.url}:")
msg_parts.append(f"type={self.err_type.name!r}")
Expand Down
9 changes: 7 additions & 2 deletions mcproto/packets/handshaking/handshake.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,15 @@


class NextState(IntEnum):
"""Enum of all possible next game states we can transition to from the :class:`Handshake` packet."""

STATUS = 1
LOGIN = 2


@final
class Handshake(ServerBoundPacket):
"""Initializes connection between server and client. (Client -> Server)"""
"""Initializes connection between server and client. (Client -> Server)."""

PACKET_ID: ClassVar[int] = 0x00
GAME_STATE: ClassVar[GameState] = GameState.HANDSHAKING
Expand All @@ -37,7 +39,8 @@ def __init__(
server_port: int,
next_state: NextState | int,
):
"""
"""Initialize the Handshake packet.
:param protocol_version: Protocol version number to be used.
:param server_address: The host/address the client is connecting to.
:param server_port: The port the client is connecting to.
Expand All @@ -56,6 +59,7 @@ def __init__(
self.next_state = next_state

def serialize(self) -> Buffer:
"""Serialize the packet."""
buf = Buffer()
buf.write_varint(self.protocol_version)
buf.write_utf(self.server_address)
Expand All @@ -65,6 +69,7 @@ def serialize(self) -> Buffer:

@classmethod
def deserialize(cls, buf: Buffer, /) -> Self:
"""Deserialize the packet."""
return cls(
protocol_version=buf.read_varint(),
server_address=buf.read_utf(),
Expand Down
Loading

0 comments on commit dd63794

Please sign in to comment.