Skip to content

Commit 30a5e57

Browse files
authored
Merge pull request #19 from offish/v2.3.2
v2.3.2
2 parents 633f5a0 + 6a60518 commit 30a5e57

File tree

7 files changed

+147
-59
lines changed

7 files changed

+147
-59
lines changed

src/tf2_utils/__init__.py

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,30 @@
1-
# flake8: noqa
21
__title__ = "tf2-utils"
32
__author__ = "offish"
4-
__version__ = "2.3.1"
3+
__version__ = "2.3.2"
54
__license__ = "MIT"
65

76
from .currency import CurrencyExchange
8-
from .exceptions import *
7+
from .exceptions import InvalidInventory, TF2UtilsError
98
from .inventory import Inventory, map_inventory
109
from .item import Item
11-
from .marketplace_tf import *
10+
from .marketplace_tf import (
11+
MarketplaceTF,
12+
MarketplaceTFException,
13+
NoAPIKey,
14+
SKUDoesNotMatch,
15+
)
1216
from .offer import Offer
13-
from .prices_tf import PricesTF
17+
from .prices_tf import (
18+
EmptyResponse,
19+
InternalServerError,
20+
PricesTF,
21+
PricesTFError,
22+
RateLimited,
23+
UnauthorizedError,
24+
)
1425
from .prices_tf_websocket import PricesTFWebsocket
1526
from .schema import SchemaItemsUtils
1627
from .sku import *
1728
from .utils import *
29+
30+
# flake8: noqa

src/tf2_utils/exceptions.py

Lines changed: 0 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,6 @@
1-
# generic
21
class TF2UtilsError(Exception):
32
pass
43

54

65
class InvalidInventory(TF2UtilsError):
76
pass
8-
9-
10-
# pricestf
11-
class PricesTFError(TF2UtilsError):
12-
pass
13-
14-
15-
class InternalServerError(PricesTFError):
16-
pass
17-
18-
19-
class RateLimited(PricesTFError):
20-
pass
21-
22-
23-
class EmptyResponse(PricesTFError):
24-
pass
25-
26-
27-
# marketplacetf
28-
class MarketplaceTFException(TF2UtilsError):
29-
pass
30-
31-
32-
class SKUDoesNotMatch(MarketplaceTFException):
33-
pass
34-
35-
36-
class NoAPIKey(MarketplaceTFException):
37-
pass

src/tf2_utils/marketplace_tf.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,23 @@
22

33
import requests
44

5-
from .exceptions import NoAPIKey, SKUDoesNotMatch
5+
from .exceptions import TF2UtilsError
66
from .schema import SchemaItemsUtils
77
from .sku import sku_is_craftable, sku_to_quality_name
88

99

10+
class MarketplaceTFException(TF2UtilsError):
11+
pass
12+
13+
14+
class SKUDoesNotMatch(MarketplaceTFException):
15+
pass
16+
17+
18+
class NoAPIKey(MarketplaceTFException):
19+
pass
20+
21+
1022
def api_key_required(func):
1123
def wrapper(self, *args, **kwargs):
1224
if self._api_key is None:

src/tf2_utils/prices_tf.py

Lines changed: 39 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,28 +3,46 @@
33

44
import requests
55

6-
from .exceptions import EmptyResponse, InternalServerError, PricesTFError, RateLimited
7-
from .utils import to_refined
6+
from .exceptions import TF2UtilsError
7+
from .utils import refinedify
88

99

10-
class PricesTF:
11-
URL = "https://api2.prices.tf"
10+
class PricesTFError(TF2UtilsError):
11+
pass
12+
13+
14+
class UnauthorizedError(PricesTFError):
15+
pass
16+
17+
18+
class InternalServerError(PricesTFError):
19+
pass
20+
21+
22+
class RateLimited(PricesTFError):
23+
pass
24+
1225

26+
class EmptyResponse(PricesTFError):
27+
pass
28+
29+
30+
class PricesTF:
1331
def __init__(self) -> None:
32+
self.url = "https://api2.prices.tf"
1433
self._access_token = ""
1534
self._headers = {}
1635

1736
@staticmethod
18-
def _format_price(data: dict) -> dict:
37+
def format_price(data: dict) -> dict:
38+
buy_keys = data.get("buyKeys", 0)
39+
buy_metal = refinedify(data.get("buyHalfScrap", 0) / 18)
40+
sell_keys = data.get("sellKeys", 0)
41+
sell_metal = refinedify(data.get("sellHalfScrap", 0) / 18)
42+
1943
return {
20-
"buy": {
21-
"keys": data["buyKeys"],
22-
"metal": to_refined(data["buyHalfScrap"] / 2),
23-
},
24-
"sell": {
25-
"keys": data["sellKeys"],
26-
"metal": to_refined(data["sellHalfScrap"] / 2),
27-
},
44+
"buy": {"keys": buy_keys, "metal": buy_metal},
45+
"sell": {"keys": sell_keys, "metal": sell_metal},
2846
}
2947

3048
@staticmethod
@@ -34,6 +52,9 @@ def _validate_response(response: dict[str, Any]) -> None:
3452

3553
status_code = response.get("statusCode")
3654

55+
if status_code == 401:
56+
raise UnauthorizedError("unauthorized, please request a new access token")
57+
3758
if status_code == 500:
3859
raise InternalServerError("there was an interal server error")
3960

@@ -44,22 +65,22 @@ def _set_header(self, header: dict) -> None:
4465
self._headers = header
4566

4667
def _get(self, endpoint: str, params: dict = {}) -> dict:
47-
url = self.URL + endpoint
68+
url = self.url + endpoint
4869
response = requests.get(url, headers=self._headers, params=params)
4970
res = response.json()
5071
self._validate_response(res)
5172

5273
return res
5374

5475
def _post(self, endpoint: str) -> tuple[dict, int]:
55-
url = self.URL + endpoint
76+
url = self.url + endpoint
5677
response = requests.post(url, headers=self._headers)
5778
res = response.json()
5879
self._validate_response(res)
5980

6081
return (res, response.status_code)
6182

62-
def _get_prices_till_page(
83+
def get_prices_till_page(
6384
self, page_limit: int, print_rate_limit: bool = False
6485
) -> dict:
6586
prices = {}
@@ -83,7 +104,7 @@ def _get_prices_till_page(
83104
raise PricesTFError("could not find any items in response")
84105

85106
for item in response["items"]:
86-
prices[item["sku"]] = self._format_price(item)
107+
prices[item["sku"]] = self.format_price(item)
87108

88109
current_page = response["meta"]["currentPage"] + 1
89110
total_pages = response["meta"]["totalPages"]
@@ -107,16 +128,14 @@ def get_prices(self, page: int, limit: int = 100, order: str = "DESC") -> dict:
107128
return self._get("/prices", {"page": page, "limit": limit, "order": order})
108129

109130
def get_all_prices(self, print_rate_limit: bool = False) -> dict:
110-
return self._get_prices_till_page(-1, print_rate_limit)
131+
return self.get_prices_till_page(-1, print_rate_limit)
111132

112133
def update_price(self, sku: str) -> tuple[dict, int]:
113134
return self._post(f"/prices/{sku}/refresh")
114135

115136
def request_access_token(self) -> None:
116137
res, _ = self._post("/auth/access")
117-
118138
self._validate_response(res)
119-
120139
self._access_token = res["accessToken"]
121140

122141
self._set_header(

src/tf2_utils/providers/express_load.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
class ExpressLoad(Provider):
55
def __init__(self, api_key: str = ""):
66
super().__init__(api_key)
7-
self.headers = {"X-API-Key": self.api_key}
7+
self.headers = {"X-API-Key": self.api_key, "User-Agent": "tf2-express"}
88

99
def get_url_and_params(
1010
self, steam_id: str, app_id: int, context_id: int

tests/test_prices_tf.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import pytest
2+
3+
from src.tf2_utils import PricesTF, UnauthorizedError
4+
5+
prices_tf = PricesTF()
6+
7+
8+
def test_inital() -> None:
9+
assert prices_tf._access_token == ""
10+
assert prices_tf._headers == {}
11+
12+
with pytest.raises(UnauthorizedError):
13+
prices_tf.get_price("5021;6")
14+
15+
prices_tf.request_access_token()
16+
17+
assert prices_tf._access_token != ""
18+
assert prices_tf._headers != {}
19+
20+
21+
def test_get_price() -> None:
22+
price = prices_tf.get_price("5021;6")
23+
24+
assert isinstance(price, dict)
25+
assert "sku" in price
26+
assert "buyHalfScrap" in price
27+
assert "buyKeys" in price
28+
assert "buyKeyHalfScrap" in price
29+
assert "sellHalfScrap" in price
30+
assert "sellKeys" in price
31+
assert "sellKeyHalfScrap" in price
32+
assert "createdAt" in price
33+
assert "updatedAt" in price
34+
35+
36+
def test_formatting_price() -> None:
37+
item = {
38+
"sku": "5021;6",
39+
"buyHalfScrap": 1210,
40+
"buyKeys": 0,
41+
"buyKeyHalfScrap": None,
42+
"sellHalfScrap": 1212,
43+
"sellKeys": 0,
44+
"sellKeyHalfScrap": None,
45+
"createdAt": "2021-10-11T23:05:32.696Z",
46+
"updatedAt": "2025-04-03T16:36:22.624Z",
47+
}
48+
formatted_price = prices_tf.format_price(item)
49+
50+
assert formatted_price["buy"]["keys"] == 0
51+
assert formatted_price["buy"]["metal"] == 67.22
52+
assert formatted_price["sell"]["keys"] == 0
53+
assert formatted_price["sell"]["metal"] == 67.33
54+
55+
56+
def test_get_prices_till_page() -> None:
57+
pages = 2
58+
prices = prices_tf.get_prices_till_page(pages)
59+
60+
assert len(prices) == pages * 50
61+
62+
for sku in prices:
63+
price = prices[sku]
64+
65+
assert isinstance(price, dict)
66+
assert "buy" in price
67+
assert "sell" in price
68+
assert "keys" in price["buy"]
69+
assert "metal" in price["buy"]
70+
assert "keys" in price["sell"]
71+
assert "metal" in price["sell"]
72+
break

tests/test_providers.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,7 @@ def test_express_load_inventory(express_load_api_key: str, steam_id: str) -> Non
2121

2222
assert url == f"https://api.express-load.com/inventory/{steam_id}/440/2"
2323
assert params == {}
24-
assert provider.headers == {"X-API-Key": express_load_api_key}
24+
assert provider.headers == {
25+
"X-API-Key": express_load_api_key,
26+
"User-Agent": "tf2-express",
27+
}

0 commit comments

Comments
 (0)