From 83d43089068e38b565bb35900ede601175a121c8 Mon Sep 17 00:00:00 2001 From: deesiigneer Date: Sun, 1 Feb 2026 14:57:16 +0000 Subject: [PATCH] refactor: enhance error handling and logging in API interactions, improve exception classes --- pyspapi/__init__.py | 50 +++++- pyspapi/api/__init__.py | 2 +- pyspapi/api/api.py | 312 ++++++++++++++++++++++++++++++++++---- pyspapi/exceptions.py | 186 ++++++++++++++++++++++- pyspapi/spworlds.py | 141 +++++++++++++---- pyspapi/types/__init__.py | 8 +- pyspapi/types/me.py | 59 ++++--- pyspapi/types/payment.py | 5 +- pyspapi/types/users.py | 18 +-- 9 files changed, 669 insertions(+), 112 deletions(-) diff --git a/pyspapi/__init__.py b/pyspapi/__init__.py index 952cd4f..bb4aa5a 100644 --- a/pyspapi/__init__.py +++ b/pyspapi/__init__.py @@ -1,13 +1,53 @@ -"""pyspapi - API wrapper for SP servers written in Python -TODO: заполнить описание""" +""" +SPWorlds API Wrapper +~~~~~~~~~~~~~~~~~~~ + +High-level client for interacting with the SPWorlds API. + +:copyright: (c) 2022-present deesiigneer +:license: MIT, see LICENSE for more details. +""" import importlib.metadata -from .spworlds import SPAPI -__all__ = [SPAPI] +from pyspapi.exceptions import ( + BadRequestError, + ClientError, + ForbiddenError, + HTTPError, + InsufficientBalanceError, + NetworkError, + NotFoundError, + RateLimitError, + ServerError, + SPAPIError, + TimeoutError, + UnauthorizedError, + ValidationError, +) +from pyspapi.spworlds import SPAPI +__all__ = [ + "SPAPI", + "BadRequestError", + "ClientError", + "ForbiddenError", + "HTTPError", + "InsufficientBalanceError", + "NetworkError", + "NotFoundError", + "RateLimitError", + "ServerError", + "SPAPIError", + "TimeoutError", + "UnauthorizedError", + "ValidationError", +] + +__title__: str = "pyspapi" __author__: str = "deesiigneer" -__url__: str = "https://github.com/deesiigneer/pyspapi" __description__: str = "API wrapper for SP servers written in Python." __license__: str = "MIT" +__url__: str = "https://github.com/deesiigneer/pyspapi" +__copyright__: str = "2022-present deesiigneer" __version__: str = importlib.metadata.version("pyspapi") diff --git a/pyspapi/api/__init__.py b/pyspapi/api/__init__.py index 91e48bb..a885f8e 100644 --- a/pyspapi/api/__init__.py +++ b/pyspapi/api/__init__.py @@ -1,3 +1,3 @@ from .api import APISession -__all__ = [APISession] +__all__ = ["APISession"] diff --git a/pyspapi/api/api.py b/pyspapi/api/api.py index 9224c6e..7d652c9 100644 --- a/pyspapi/api/api.py +++ b/pyspapi/api/api.py @@ -1,14 +1,31 @@ import asyncio import json from base64 import b64encode -from logging import getLogger -from typing import Optional, Any, Dict +from logging import NullHandler, getLogger +from typing import Any, Dict, Optional import aiohttp -from ..exceptions import ValidationError, SPAPIError +from pyspapi.exceptions import ( + BadRequestError, + ClientError, + ForbiddenError, + HTTPError, + InsufficientBalanceError, + NetworkError, + NotFoundError, + RateLimitError, + ServerError, + SPAPIError, + UnauthorizedError, + ValidationError, +) +from pyspapi.exceptions import ( + TimeoutError as APITimeoutError, +) log = getLogger("pyspapi") +log.addHandler(NullHandler()) class APISession(object): @@ -22,6 +39,8 @@ class APISession(object): raise_exception: bool = False, proxy: str = None, ): + self._validate_credentials(card_id, token) + self.__url = "https://spworlds.ru/api/public/" self.__id = card_id self.__token = token @@ -31,61 +50,294 @@ class APISession(object): self.__raise_exception = raise_exception self.__proxy = proxy self.session: Optional[aiohttp.ClientSession] = None + self._session_owner = False + + @staticmethod + def _validate_credentials(card_id: str, token: str) -> None: + if not card_id or not isinstance(card_id, str): + raise ValueError("card_id must be a non-empty string") + if not token or not isinstance(token, str): + raise ValueError("token must be a non-empty string") async def __aenter__(self): - print("proxy=", self.__proxy) - self.session = aiohttp.ClientSession( - json_serialize=json.dumps, - timeout=aiohttp.ClientTimeout(total=self.__timeout), - proxy=self.__proxy, - ) + try: + if not self.session: + self.session = aiohttp.ClientSession( + json_serialize=json.dumps, + timeout=aiohttp.ClientTimeout(total=self.__timeout), + proxy=self.__proxy, + ) + self._session_owner = True + log.debug(f"[pyspapi] Session created with timeout={self.__timeout}s") + else: + self._session_owner = False + except Exception as e: + log.error(f"[pyspapi] Failed to create session: {e}") + raise return self - async def __aexit__(self, *err): - await self.session.close() - self.session = None + async def __aexit__(self, exc_type, exc_val, exc_tb): + if self._session_owner and self.session: + try: + await self.session.close() + log.debug("[pyspapi] Session closed") + except Exception as e: + log.error(f"[pyspapi] Error closing session: {e}") + self.session = None + + return False + + def _get_auth_header(self) -> str: + credentials = f"{self.__id}:{self.__token}" + encoded = b64encode(credentials.encode("utf-8")).decode("utf-8") + return f"Bearer {encoded}" + + def _get_headers(self) -> Dict[str, str]: + return { + "Authorization": self._get_auth_header(), + "User-Agent": "https://github.com/deesiigneer/pyspapi", + "Content-Type": "application/json", + } + + def _parse_error_response(self, content: str) -> Dict[str, Any]: + try: + return json.loads(content) + except json.JSONDecodeError: + return {"raw_response": content} + + def _format_error_message( + self, error_data: Dict[str, Any], status_code: int + ) -> str: + message = ( + error_data.get("message") + or error_data.get("detail") + or error_data.get("msg") + or f"HTTP {status_code}" + ) + + if "error" in error_data: + message = f"{message} (error: {error_data['error']})" + + return message + + def _log_error_with_details( + self, + method: str, + endpoint: str, + status_code: int, + error_data: Dict[str, Any], + content: str, + ) -> None: + message = self._format_error_message(error_data, status_code) + + log.error( + f"[pyspapi] HTTP {status_code}: {method.upper()} {endpoint} | {message}" + ) + + def _should_retry(self, status_code: int, attempt: int) -> bool: + if attempt > self.__retries: + return False + return status_code in {408, 429, 500, 502, 503, 504} + + async def _handle_http_error( + self, + method: str, + endpoint: str, + status_code: int, + content: str, + ) -> None: + error_data = self._parse_error_response(content) + + self._log_error_with_details(method, endpoint, status_code, error_data, content) + + if not self.__raise_exception: + return + + error_message = self._format_error_message(error_data, status_code) + + if status_code == 400: + error_code = error_data.get("error", "") + if "notEnoughBalance" in error_code: + raise InsufficientBalanceError(details=error_data) + raise BadRequestError(details=error_data) + elif status_code == 401: + raise UnauthorizedError(details=error_data) + elif status_code == 403: + raise ForbiddenError(details=error_data) + elif status_code == 404: + raise NotFoundError(resource=endpoint, details=error_data) + elif status_code == 422: + raise ValidationError(error_data) + elif status_code == 429: + retry_after = error_data.get("retry_after") + raise RateLimitError(retry_after=retry_after, details=error_data) + elif 400 <= status_code < 500: + raise ClientError( + status_code=status_code, + message=error_message, + response_body=content, + details=error_data, + ) + elif 500 <= status_code < 600: + raise ServerError( + status_code=status_code, + message=error_message, + response_body=content, + details=error_data, + ) + else: + raise HTTPError( + status_code=status_code, + message=error_message, + response_body=content, + details=error_data, + ) async def request( self, method: str, endpoint: str, data: Optional[Dict] = None ) -> Any: url = self.__url + endpoint - headers = { - "Authorization": f"Bearer {str(b64encode(str(f'{self.__id}:{self.__token}').encode('utf-8')), 'utf-8')}", - "User-Agent": "https://github.com/deesiigneer/pyspapi", - "Content-Type": "application/json", - } + headers = self._get_headers() attempt = 0 + while True: attempt += 1 if attempt > 1: log.warning( - f"[pyspapi] Repeat attempt {attempt}: {method.upper()} {url}" + f"[pyspapi] Retry attempt {attempt}/{self.__retries + 1}: {method.upper()} {endpoint}" ) + try: async with self.session.request( method, url, json=data, headers=headers ) as resp: + response_text = await resp.text() + if resp.status == 422: - errors = await resp.json() - log.error(f"[pyspapi] Validation error: {errors}") + try: + errors = json.loads(response_text) + except json.JSONDecodeError: + errors = {"raw_response": response_text} + + error_msg = self._format_error_message(errors, 422) + log.error( + f"[pyspapi] Validation error (422): {method.upper()} {endpoint} | {error_msg}" + ) if self.__raise_exception: raise ValidationError(errors) return None if resp.status >= 400: - content = await resp.text() - log.error(f"[pyspapi] API error {resp.status}: {content}") - if self.__raise_exception: - raise SPAPIError(resp.status, content) + await self._handle_http_error( + method, endpoint, resp.status, response_text + ) + + if self._should_retry(resp.status, attempt): + await asyncio.sleep(self.__sleep_timeout * attempt) + continue + return None - return await resp.json() - except (aiohttp.ClientError, asyncio.TimeoutError) as e: - log.exception(f"[pyspapi] Connection error: {e} \n attempt {attempt}") - if attempt > self.__retries: - return None - await asyncio.sleep(self.__sleep_timeout) + try: + return await resp.json() + except json.JSONDecodeError as e: + log.error( + f"[pyspapi] Failed to parse JSON response: {e} | Status: {resp.status}" + ) + if self.__raise_exception: + raise SPAPIError( + status_code=resp.status, + message="Invalid JSON in response", + details={ + "error": str(e), + "response": response_text[:500], + }, + ) + return None + + except asyncio.TimeoutError: + log.warning( + f"[pyspapi] Request timeout ({self.__timeout}s): {method.upper()} {endpoint} | Attempt {attempt}/{self.__retries + 1}" + ) + + if self._should_retry(408, attempt): + await asyncio.sleep(self.__sleep_timeout * attempt) + continue + + log.error("[pyspapi] Max retries reached for timeout") + if self.__raise_exception: + raise APITimeoutError( + timeout=self.__timeout, + endpoint=endpoint, + details={"method": method, "attempt": attempt}, + ) + return None + + except aiohttp.ClientSSLError as e: + log.error(f"[pyspapi] SSL error: {e} | {method.upper()} {endpoint}") + if self.__raise_exception: + raise NetworkError( + message=f"SSL error: {str(e)}", + details={ + "method": method, + "endpoint": endpoint, + "error": str(e), + }, + ) + return None + + except (aiohttp.ClientConnectorError, aiohttp.ClientOSError) as e: + log.warning( + f"[pyspapi] Connection error: {e} | {method.upper()} {endpoint} | Attempt {attempt}/{self.__retries + 1}" + ) + + if self._should_retry(0, attempt): + await asyncio.sleep(self.__sleep_timeout * attempt) + continue + + log.error("[pyspapi] Max retries reached for connection error") + if self.__raise_exception: + raise NetworkError( + message=f"Connection error: {str(e)}", + details={ + "method": method, + "endpoint": endpoint, + "error": str(e), + "attempt": attempt, + }, + ) + return None + + except aiohttp.ClientError as e: + log.error(f"[pyspapi] Client error: {e} | {method.upper()} {endpoint}") + if self.__raise_exception: + raise NetworkError( + message=f"HTTP client error: {str(e)}", + details={ + "method": method, + "endpoint": endpoint, + "error": str(e), + }, + ) + return None + + except SPAPIError: + raise + except Exception as e: + log.exception( + f"[pyspapi] Unexpected error: {e} | {method.upper()} {endpoint}" + ) + if self.__raise_exception: + raise SPAPIError( + message=f"Unexpected error: {str(e)}", + details={ + "error": str(e), + "method": method, + "endpoint": endpoint, + }, + ) + return None async def get(self, endpoint: str) -> Any: async with self: diff --git a/pyspapi/exceptions.py b/pyspapi/exceptions.py index b4b66b1..43c22ea 100644 --- a/pyspapi/exceptions.py +++ b/pyspapi/exceptions.py @@ -1,25 +1,197 @@ +from typing import Any, Dict, Optional + + class SPAPIError(Exception): """ Базовая ошибка для всех исключений, связанных с API SPWorlds. """ - def __init__(self, status_code: int, message: str): + def __init__( + self, + status_code: Optional[int] = None, + message: str = "", + details: Optional[Dict[str, Any]] = None, + ): self.status_code = status_code self.message = message - super().__init__(f"[{status_code}] {message}") + self.details = details or {} + error_msg = f"[{status_code}] {message}" if status_code else message + super().__init__(error_msg) def __str__(self): - return f"SPAPIError: [{self.status_code}] {self.message}" + if self.status_code: + return f"{self.__class__.__name__}: [{self.status_code}] {self.message}" + return f"{self.__class__.__name__}: {self.message}" + + def __repr__(self): + return f"{self.__class__.__name__}(status_code={self.status_code}, message={self.message!r}, details={self.details!r})" class ValidationError(SPAPIError): """ - Ошибка валидации. + Ошибка валидации (HTTP 422). """ - def __init__(self, errors): + def __init__(self, errors: Dict[str, Any]): self.errors = errors - super().__init__(422, f"Validation failed: {errors}") + super().__init__( + status_code=422, + message="Validation failed", + details={"validation_errors": errors}, + ) def __str__(self): - return f"ValidationError: {self.errors}" + return f"{self.__class__.__name__}: {self.errors}" + + +class NetworkError(SPAPIError): + """ + Ошибка сетевого соединения. + """ + + def __init__(self, message: str, details: Optional[Dict[str, Any]] = None): + super().__init__(message=message, details=details) + + +class TimeoutError(SPAPIError): + """ + Ошибка истечения времени ожидания. + """ + + def __init__( + self, + timeout: float, + endpoint: str = "", + details: Optional[Dict[str, Any]] = None, + ): + msg = f"Request timeout after {timeout}s" + if endpoint: + msg += f" for endpoint: {endpoint}" + super().__init__( + message=msg, details=details or {"timeout": timeout, "endpoint": endpoint} + ) + + +class HTTPError(SPAPIError): + """ + Ошибка HTTP (4xx, 5xx). + """ + + def __init__( + self, + status_code: int, + message: str = "", + response_body: str = "", + details: Optional[Dict[str, Any]] = None, + ): + self.response_body = response_body + super().__init__( + status_code=status_code, + message=message or f"HTTP {status_code}", + details=details or {"response_body": response_body}, + ) + + +class ClientError(HTTPError): + """ + Ошибка клиента (4xx). + """ + + pass + + +class ServerError(HTTPError): + """ + Ошибка сервера (5xx). + """ + + pass + + +class RateLimitError(ClientError): + """ + Превышен лимит запросов (HTTP 429). + """ + + def __init__( + self, + retry_after: Optional[int] = None, + details: Optional[Dict[str, Any]] = None, + ): + self.retry_after = retry_after + msg = "Rate limit exceeded" + if retry_after: + msg += f". Retry after {retry_after}s" + super().__init__( + status_code=429, + message=msg, + details=details or {"retry_after": retry_after}, + ) + + +class UnauthorizedError(ClientError): + """ + Ошибка аутентификации (HTTP 401). + """ + + def __init__(self, details: Optional[Dict[str, Any]] = None): + super().__init__( + status_code=401, + message="Unauthorized. Invalid or missing credentials.", + details=details, + ) + + +class ForbiddenError(ClientError): + """ + Ошибка доступа (HTTP 403). + """ + + def __init__(self, details: Optional[Dict[str, Any]] = None): + super().__init__( + status_code=403, + message="Forbidden. Access denied.", + details=details, + ) + + +class NotFoundError(ClientError): + """ + Ресурс не найден (HTTP 404). + """ + + def __init__(self, resource: str = "", details: Optional[Dict[str, Any]] = None): + msg = "Resource not found" + if resource: + msg += f": {resource}" + super().__init__( + status_code=404, + message=msg, + details=details or {"resource": resource}, + ) + + +class BadRequestError(ClientError): + """ + Некорректный запрос (HTTP 400). + """ + + def __init__(self, details: Optional[Dict[str, Any]] = None): + super().__init__( + status_code=400, + message="Bad request. Invalid request parameters.", + details=details, + ) + + +class InsufficientBalanceError(ClientError): + """ + Недостаточно средств на счете. + """ + + def __init__(self, details: Optional[Dict[str, Any]] = None): + super().__init__( + status_code=400, + message="Insufficient balance. Not enough funds to complete the transaction.", + details=details or {"error": "error.public.transactions.notEnoughBalance"}, + ) diff --git a/pyspapi/spworlds.py b/pyspapi/spworlds.py index a0c4674..29b8dee 100644 --- a/pyspapi/spworlds.py +++ b/pyspapi/spworlds.py @@ -1,9 +1,10 @@ from base64 import b64encode from hashlib import sha256 -from hmac import new, compare_digest +from hmac import compare_digest, new from typing import Optional -from .api import APISession +from pyspapi.api import APISession +from pyspapi.exceptions import InsufficientBalanceError from pyspapi.types import User from pyspapi.types.me import Account from pyspapi.types.payment import Item @@ -44,7 +45,7 @@ class SPAPI(APISession): :type retries: int :param raise_exception: Поднимать исключения при ошибке, если True. :type raise_exception: bool - :param proxy: Прокся! + :param proxy: Прокси для подключения к API. По умолчанию None. :type proxy: str """ super().__init__( @@ -67,7 +68,15 @@ class SPAPI(APISession): :return: Текущий баланс карты. :rtype: int """ - return int((await super().get("card"))["balance"]) + try: + response = await super().get("card") + if response is None: + return None + return int(response.get("balance", 0)) + except (KeyError, ValueError, TypeError) as e: + log = __import__("logging").getLogger("pyspapi") + log.error(f"Failed to parse balance response: {e}") + return None @property async def webhook(self) -> Optional[str]: @@ -77,7 +86,15 @@ class SPAPI(APISession): :return: URL вебхука. :rtype: str """ - return str((await super().get("card"))["webhook"]) + try: + response = await super().get("card") + if response is None: + return None + return str(response.get("webhook", "")) + except (KeyError, ValueError, TypeError) as e: + log = __import__("logging").getLogger("pyspapi") + log.error(f"Failed to parse webhook response: {e}") + return None @property async def me(self) -> Optional[Account]: @@ -87,17 +104,25 @@ class SPAPI(APISession): :return: Объект Account, представляющий аккаунт текущего пользователя. :rtype: :class:`Account` """ - me = await super().get("accounts/me") - return Account( - account_id=me["id"], - username=me["username"], - minecraftuuid=me["minecraftUUID"], - status=me["status"], - roles=me["roles"], - cities=me["cities"], - cards=me["cards"], - created_at=me["createdAt"], - ) + try: + me = await super().get("accounts/me") + if me is None: + return None + + return Account( + account_id=me.get("id"), + username=me.get("username"), + minecraftuuid=me.get("minecraftUUID"), + status=me.get("status"), + roles=me.get("roles", []), + cities=me.get("cities", []), + cards=me.get("cards", []), + created_at=me.get("createdAt"), + ) + except (KeyError, TypeError) as e: + log = __import__("logging").getLogger("pyspapi") + log.error(f"Failed to parse account response: {e}") + return None async def get_user(self, discord_id: int) -> Optional[User]: """ @@ -109,11 +134,22 @@ class SPAPI(APISession): :return: Объект User, представляющий пользователя. :rtype: :class:`User` """ - user = await super().get(f"users/{discord_id}") - if user: + if not discord_id: + raise ValueError("discord_id must be a non-empty integer") + + try: + user = await super().get(f"users/{discord_id}") + if user is None: + return None + cards = await super().get(f"accounts/{user['username']}/cards") + if cards is None: + cards = [] + return User(user["username"], user["uuid"], cards) - else: + except (KeyError, TypeError) as e: + log = __import__("logging").getLogger("pyspapi") + log.error(f"Failed to parse user response: {e}") return None async def create_transaction( @@ -132,9 +168,27 @@ class SPAPI(APISession): :return: Баланс после транзакции. :rtype: int """ - data = {"receiver": receiver, "amount": amount, "comment": comment} + if not receiver: + raise ValueError("receiver must be a non-empty string") + if not isinstance(amount, int) or amount <= 0: + raise ValueError("amount must be a positive integer") - return int((await super().post("transactions", data))["balance"]) + try: + data = {"receiver": receiver, "amount": amount, "comment": comment} + response = await super().post("transactions", data) + + if response is None: + return None + + return int(response.get("balance", 0)) + except (KeyError, ValueError, TypeError) as e: + log = __import__("logging").getLogger("pyspapi") + log.error(f"Failed to create transaction: {e}") + return None + except InsufficientBalanceError as ibe: + log = __import__("logging").getLogger("pyspapi") + log.error(f"Insufficient balance for transaction: {ibe}") + return None async def create_payment( self, webhook_url: str, redirect_url: str, data: str, items: list[Item] @@ -153,14 +207,29 @@ class SPAPI(APISession): :return: URL для платежа или None при ошибке. :rtype: str """ - data = { - "items": items, - "redirectUrl": redirect_url, - "webhookUrl": webhook_url, - "data": data, - } + if not webhook_url or not redirect_url: + raise ValueError("webhook_url and redirect_url must be non-empty strings") + if not items or len(items) == 0: + raise ValueError("items must contain at least one item") - return str((await super().post("payments", data))["url"]) + try: + payload = { + "items": items, + "redirectUrl": redirect_url, + "webhookUrl": webhook_url, + "data": data, + } + + response = await super().post("payments", payload) + + if response is None: + return None + + return str(response.get("url", "")) + except (KeyError, ValueError, TypeError) as e: + log = __import__("logging").getLogger("pyspapi") + log.error(f"Failed to create payment: {e}") + return None async def update_webhook(self, url: str) -> Optional[dict]: """ @@ -169,9 +238,21 @@ class SPAPI(APISession): :param url: Новый URL вебхука. :return: Ответ API в виде словаря или None при ошибке. """ - data = {"url": url} + if not url: + raise ValueError("url must be a non-empty string") - return await super().put("card/webhook", data) + try: + data = {"url": url} + response = await super().put("card/webhook", data) + + if response is None: + return None + + return response + except (KeyError, TypeError) as e: + log = __import__("logging").getLogger("pyspapi") + log.error(f"Failed to update webhook: {e}") + return None def webhook_verify(self, data: str, header: str) -> bool: """ diff --git a/pyspapi/types/__init__.py b/pyspapi/types/__init__.py index 70ed719..a03eb1b 100644 --- a/pyspapi/types/__init__.py +++ b/pyspapi/types/__init__.py @@ -1,5 +1,5 @@ -from .me import Account -from .payment import Item -from .users import Cards, User +from pyspapi.types.me import Account +from pyspapi.types.payment import Item +from pyspapi.types.users import Cards, User -__all__ = [Account, Item, Cards, User] +__all__ = ["Account", "Item", "Cards", "User"] diff --git a/pyspapi/types/me.py b/pyspapi/types/me.py index e620b65..68ee0a5 100644 --- a/pyspapi/types/me.py +++ b/pyspapi/types/me.py @@ -1,6 +1,16 @@ class City: - def __init__(self, city_id=None, name=None, x=None, z=None, nether_x=None, nether_z=None, lane=None, role=None, - created_at=None): + def __init__( + self, + city_id=None, + name=None, + x=None, + z=None, + nether_x=None, + nether_z=None, + lane=None, + role=None, + created_at=None, + ): self._id = city_id self._name = name self._x = x @@ -48,11 +58,7 @@ class City: return self._created_at def __repr__(self): - return ( - f"City(id={self._id}, name={self._name}, x={self._x}, z={self._z}, " - f"nether_x={self._nether_x}, nether_z={self._nether_z}, lane={self._lane}, role={self._role}, " - f"created_at={self._created_at})" - ) + return f"<{self.__class__.__name__}(id={self._id!r}, name={self._name!r}, lane={self._lane!r}, role={self._role!r})>" class Card: @@ -79,11 +85,21 @@ class Card: return self._color def __repr__(self): - return f"Card(id={self._id}, name={self._name}, number={self._number}, color={self._color})" + return f"<{self.__class__.__name__}(id={self._id!r}, name={self._name!r}, number={self._number!r})>" class Account: - def __init__(self, account_id, username, minecraftuuid, status, roles, created_at, cards, cities): + def __init__( + self, + account_id, + username, + minecraftuuid, + status, + roles, + created_at, + cards, + cities, + ): self._id = account_id self._username = username self._minecraftuuid = minecraftuuid @@ -91,15 +107,15 @@ class Account: self._roles = roles self._cities = [ City( - city_id=city['city_id'], - name=city['name'], - x=city['x'], - z=city['z'], - nether_x=city['nether_x'], - nether_z=city['nether_z'], - lane=city['lane'], - role=city['role'], - created_at=city['created_at'], + city_id=city["city"]["id"], + name=city["city"]["name"], + x=city["city"]["x"], + z=city["city"]["z"], + nether_x=city["city"]["netherX"], + nether_z=city["city"]["netherZ"], + lane=city["city"]["lane"], + role=city["role"], + created_at=city["createdAt"], ) for city in cities ] @@ -147,6 +163,7 @@ class Account: return self._created_at def __repr__(self): - return (f"Account(id={self._id}, username={self._username}, minecraftUUID={self._minecraftuuid}, " - f"status={self._status}, roles={self._roles}, cities={self._cities}, cards={self._cards}, " - f"created_at={self._created_at})") + return ( + f"<{self.__class__.__name__}(id={self._id!r}, username={self._username!r}, status={self._status!r}, " + f"roles={self._roles}, cities={self._cities}, cards={self._cards})>" + ) diff --git a/pyspapi/types/payment.py b/pyspapi/types/payment.py index ba62098..36db35b 100644 --- a/pyspapi/types/payment.py +++ b/pyspapi/types/payment.py @@ -9,10 +9,13 @@ class Item: def name(self): return self._name + def __repr__(self): + return f"<{self.__class__.__name__}(name={self._name!r}, count={self._count!r}, price={self._price!r}, comment={self._comment!r})>" + def to_json(self): return { "name": self._name, "count": self._count, "price": self._price, - "comment": self._comment + "comment": self._comment, } diff --git a/pyspapi/types/users.py b/pyspapi/types/users.py index 962e53a..02bb089 100644 --- a/pyspapi/types/users.py +++ b/pyspapi/types/users.py @@ -1,5 +1,4 @@ -class Cards: - +class UserCards: def __init__(self, name, number): self._name: str = name self._number: str = number @@ -12,20 +11,16 @@ class Cards: def number(self): return self._number - # def __repr__(self): - # return "%s(%s)" % ( - # self.__class__.__name__, - # self.__dict__ - # ) + def __repr__(self): + return f"<{self.__class__.__name__}(id={self._id!r}, name={self._name!r}, number={self._number!r})>" class User: - def __init__(self, username, uuid, cards): self._username: str = username self._uuid: str = uuid self._cards = [ - Cards( + UserCards( name=card["name"], number=card["number"], ) @@ -45,7 +40,4 @@ class User: return self._cards def __repr__(self): - return "%s(%s)" % ( - self.__class__.__name__, - self.__dict__ - ) + return "%s(%s)" % (self.__class__.__name__, self.__dict__)