From 6a605181299c1f2b05b0e8c01944e04f4e696334 Mon Sep 17 00:00:00 2001 From: offish Date: Thu, 3 Apr 2025 20:33:28 +0200 Subject: [PATCH] small changes --- src/tf2_utils/__init__.py | 23 ++++++-- src/tf2_utils/exceptions.py | 31 ----------- src/tf2_utils/marketplace_tf.py | 14 ++++- src/tf2_utils/prices_tf.py | 59 +++++++++++++------- src/tf2_utils/providers/express_load.py | 2 +- tests/test_prices_tf.py | 72 +++++++++++++++++++++++++ tests/test_providers.py | 5 +- 7 files changed, 147 insertions(+), 59 deletions(-) create mode 100644 tests/test_prices_tf.py diff --git a/src/tf2_utils/__init__.py b/src/tf2_utils/__init__.py index 91f92ae..c250f35 100644 --- a/src/tf2_utils/__init__.py +++ b/src/tf2_utils/__init__.py @@ -1,17 +1,30 @@ -# flake8: noqa __title__ = "tf2-utils" __author__ = "offish" -__version__ = "2.3.1" +__version__ = "2.3.2" __license__ = "MIT" from .currency import CurrencyExchange -from .exceptions import * +from .exceptions import InvalidInventory, TF2UtilsError from .inventory import Inventory, map_inventory from .item import Item -from .marketplace_tf import * +from .marketplace_tf import ( + MarketplaceTF, + MarketplaceTFException, + NoAPIKey, + SKUDoesNotMatch, +) from .offer import Offer -from .prices_tf import PricesTF +from .prices_tf import ( + EmptyResponse, + InternalServerError, + PricesTF, + PricesTFError, + RateLimited, + UnauthorizedError, +) from .prices_tf_websocket import PricesTFWebsocket from .schema import SchemaItemsUtils from .sku import * from .utils import * + +# flake8: noqa diff --git a/src/tf2_utils/exceptions.py b/src/tf2_utils/exceptions.py index 3b15395..0715f0a 100644 --- a/src/tf2_utils/exceptions.py +++ b/src/tf2_utils/exceptions.py @@ -1,37 +1,6 @@ -# generic class TF2UtilsError(Exception): pass class InvalidInventory(TF2UtilsError): pass - - -# pricestf -class PricesTFError(TF2UtilsError): - pass - - -class InternalServerError(PricesTFError): - pass - - -class RateLimited(PricesTFError): - pass - - -class EmptyResponse(PricesTFError): - pass - - -# marketplacetf -class MarketplaceTFException(TF2UtilsError): - pass - - -class SKUDoesNotMatch(MarketplaceTFException): - pass - - -class NoAPIKey(MarketplaceTFException): - pass diff --git a/src/tf2_utils/marketplace_tf.py b/src/tf2_utils/marketplace_tf.py index 9799c86..a352157 100644 --- a/src/tf2_utils/marketplace_tf.py +++ b/src/tf2_utils/marketplace_tf.py @@ -2,11 +2,23 @@ import requests -from .exceptions import NoAPIKey, SKUDoesNotMatch +from .exceptions import TF2UtilsError from .schema import SchemaItemsUtils from .sku import sku_is_craftable, sku_to_quality_name +class MarketplaceTFException(TF2UtilsError): + pass + + +class SKUDoesNotMatch(MarketplaceTFException): + pass + + +class NoAPIKey(MarketplaceTFException): + pass + + def api_key_required(func): def wrapper(self, *args, **kwargs): if self._api_key is None: diff --git a/src/tf2_utils/prices_tf.py b/src/tf2_utils/prices_tf.py index 6d64016..71b75ca 100644 --- a/src/tf2_utils/prices_tf.py +++ b/src/tf2_utils/prices_tf.py @@ -3,28 +3,46 @@ import requests -from .exceptions import EmptyResponse, InternalServerError, PricesTFError, RateLimited -from .utils import to_refined +from .exceptions import TF2UtilsError +from .utils import refinedify -class PricesTF: - URL = "https://api2.prices.tf" +class PricesTFError(TF2UtilsError): + pass + + +class UnauthorizedError(PricesTFError): + pass + + +class InternalServerError(PricesTFError): + pass + + +class RateLimited(PricesTFError): + pass + +class EmptyResponse(PricesTFError): + pass + + +class PricesTF: def __init__(self) -> None: + self.url = "https://api2.prices.tf" self._access_token = "" self._headers = {} @staticmethod - def _format_price(data: dict) -> dict: + def format_price(data: dict) -> dict: + buy_keys = data.get("buyKeys", 0) + buy_metal = refinedify(data.get("buyHalfScrap", 0) / 18) + sell_keys = data.get("sellKeys", 0) + sell_metal = refinedify(data.get("sellHalfScrap", 0) / 18) + return { - "buy": { - "keys": data["buyKeys"], - "metal": to_refined(data["buyHalfScrap"] / 2), - }, - "sell": { - "keys": data["sellKeys"], - "metal": to_refined(data["sellHalfScrap"] / 2), - }, + "buy": {"keys": buy_keys, "metal": buy_metal}, + "sell": {"keys": sell_keys, "metal": sell_metal}, } @staticmethod @@ -34,6 +52,9 @@ def _validate_response(response: dict[str, Any]) -> None: status_code = response.get("statusCode") + if status_code == 401: + raise UnauthorizedError("unauthorized, please request a new access token") + if status_code == 500: raise InternalServerError("there was an interal server error") @@ -44,7 +65,7 @@ def _set_header(self, header: dict) -> None: self._headers = header def _get(self, endpoint: str, params: dict = {}) -> dict: - url = self.URL + endpoint + url = self.url + endpoint response = requests.get(url, headers=self._headers, params=params) res = response.json() self._validate_response(res) @@ -52,14 +73,14 @@ def _get(self, endpoint: str, params: dict = {}) -> dict: return res def _post(self, endpoint: str) -> tuple[dict, int]: - url = self.URL + endpoint + url = self.url + endpoint response = requests.post(url, headers=self._headers) res = response.json() self._validate_response(res) return (res, response.status_code) - def _get_prices_till_page( + def get_prices_till_page( self, page_limit: int, print_rate_limit: bool = False ) -> dict: prices = {} @@ -83,7 +104,7 @@ def _get_prices_till_page( raise PricesTFError("could not find any items in response") for item in response["items"]: - prices[item["sku"]] = self._format_price(item) + prices[item["sku"]] = self.format_price(item) current_page = response["meta"]["currentPage"] + 1 total_pages = response["meta"]["totalPages"] @@ -107,16 +128,14 @@ def get_prices(self, page: int, limit: int = 100, order: str = "DESC") -> dict: return self._get("/prices", {"page": page, "limit": limit, "order": order}) def get_all_prices(self, print_rate_limit: bool = False) -> dict: - return self._get_prices_till_page(-1, print_rate_limit) + return self.get_prices_till_page(-1, print_rate_limit) def update_price(self, sku: str) -> tuple[dict, int]: return self._post(f"/prices/{sku}/refresh") def request_access_token(self) -> None: res, _ = self._post("/auth/access") - self._validate_response(res) - self._access_token = res["accessToken"] self._set_header( diff --git a/src/tf2_utils/providers/express_load.py b/src/tf2_utils/providers/express_load.py index 39608df..d31c020 100644 --- a/src/tf2_utils/providers/express_load.py +++ b/src/tf2_utils/providers/express_load.py @@ -4,7 +4,7 @@ class ExpressLoad(Provider): def __init__(self, api_key: str = ""): super().__init__(api_key) - self.headers = {"X-API-Key": self.api_key} + self.headers = {"X-API-Key": self.api_key, "User-Agent": "tf2-express"} def get_url_and_params( self, steam_id: str, app_id: int, context_id: int diff --git a/tests/test_prices_tf.py b/tests/test_prices_tf.py new file mode 100644 index 0000000..328a200 --- /dev/null +++ b/tests/test_prices_tf.py @@ -0,0 +1,72 @@ +import pytest + +from src.tf2_utils import PricesTF, UnauthorizedError + +prices_tf = PricesTF() + + +def test_inital() -> None: + assert prices_tf._access_token == "" + assert prices_tf._headers == {} + + with pytest.raises(UnauthorizedError): + prices_tf.get_price("5021;6") + + prices_tf.request_access_token() + + assert prices_tf._access_token != "" + assert prices_tf._headers != {} + + +def test_get_price() -> None: + price = prices_tf.get_price("5021;6") + + assert isinstance(price, dict) + assert "sku" in price + assert "buyHalfScrap" in price + assert "buyKeys" in price + assert "buyKeyHalfScrap" in price + assert "sellHalfScrap" in price + assert "sellKeys" in price + assert "sellKeyHalfScrap" in price + assert "createdAt" in price + assert "updatedAt" in price + + +def test_formatting_price() -> None: + item = { + "sku": "5021;6", + "buyHalfScrap": 1210, + "buyKeys": 0, + "buyKeyHalfScrap": None, + "sellHalfScrap": 1212, + "sellKeys": 0, + "sellKeyHalfScrap": None, + "createdAt": "2021-10-11T23:05:32.696Z", + "updatedAt": "2025-04-03T16:36:22.624Z", + } + formatted_price = prices_tf.format_price(item) + + assert formatted_price["buy"]["keys"] == 0 + assert formatted_price["buy"]["metal"] == 67.22 + assert formatted_price["sell"]["keys"] == 0 + assert formatted_price["sell"]["metal"] == 67.33 + + +def test_get_prices_till_page() -> None: + pages = 2 + prices = prices_tf.get_prices_till_page(pages) + + assert len(prices) == pages * 50 + + for sku in prices: + price = prices[sku] + + assert isinstance(price, dict) + assert "buy" in price + assert "sell" in price + assert "keys" in price["buy"] + assert "metal" in price["buy"] + assert "keys" in price["sell"] + assert "metal" in price["sell"] + break diff --git a/tests/test_providers.py b/tests/test_providers.py index 312da80..4e6d24b 100644 --- a/tests/test_providers.py +++ b/tests/test_providers.py @@ -21,4 +21,7 @@ def test_express_load_inventory(express_load_api_key: str, steam_id: str) -> Non assert url == f"https://api.express-load.com/inventory/{steam_id}/440/2" assert params == {} - assert provider.headers == {"X-API-Key": express_load_api_key} + assert provider.headers == { + "X-API-Key": express_load_api_key, + "User-Agent": "tf2-express", + }