From 54d76258b4e6477c4d7cbe52bdf77d8c27d149db Mon Sep 17 00:00:00 2001 From: offish Date: Mon, 28 Aug 2023 18:31:49 +0200 Subject: [PATCH] v2.0.0 (#3) * rewrite and new version * small fixes and documentation * update version number --- .github/workflows/publish.yml | 17 ++++ .gitignore | 1 + README.md | 75 +++++++++++++++ README.rst | 121 ------------------------ example.py | 34 ------- pyproject.toml | 26 ++++++ setup.cfg | 2 - setup.py | 31 +------ {tf2_sku => src/tf2_sku}/__init__.py | 11 +-- src/tf2_sku/sku.py | 75 +++++++++++++++ src/tf2_sku/utils.py | 57 ++++++++++++ tests/__init__.py | 0 tests/test_sku.py | 67 ++++++++++++++ tf2_sku/prettify.py | 38 -------- tf2_sku/sku.py | 132 --------------------------- 15 files changed, 323 insertions(+), 364 deletions(-) create mode 100644 .github/workflows/publish.yml create mode 100644 .gitignore create mode 100644 README.md delete mode 100644 README.rst delete mode 100644 example.py create mode 100644 pyproject.toml delete mode 100644 setup.cfg rename {tf2_sku => src/tf2_sku}/__init__.py (57%) create mode 100644 src/tf2_sku/sku.py create mode 100644 src/tf2_sku/utils.py create mode 100644 tests/__init__.py create mode 100644 tests/test_sku.py delete mode 100644 tf2_sku/prettify.py delete mode 100644 tf2_sku/sku.py diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..1756a17 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,17 @@ +name: Upload Python Package +on: + release: + types: [published] +jobs: + pypi: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 0 + - run: python3 -m pip install --upgrade build && python3 -m build + - name: Publish package + uses: pypa/gh-action-pypi-publish@release/v1 + with: + password: ${{ secrets.PYPI_API_TOKEN }} \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0041fb8 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +__pycache__ diff --git a/README.md b/README.md new file mode 100644 index 0000000..df16b54 --- /dev/null +++ b/README.md @@ -0,0 +1,75 @@ +# tf2-sku +[![License](https://img.shields.io/github/license/offish/tf2-sku.svg)](https://github.com/offish/tf2-sku/blob/master/LICENSE) +[![Stars](https://img.shields.io/github/stars/offish/tf2-sku.svg)](https://github.com/offish/tf2-sku/stargazers) +[![Issues](https://img.shields.io/github/issues/offish/tf2-sku.svg)](https://github.com/offish/tf2-sku/issues) +[![Size](https://img.shields.io/github/repo-size/offish/tf2-sku.svg)](https://github.com/offish/tf2-sku) +[![Discord](https://img.shields.io/discord/467040686982692865?color=7289da&label=Discord&logo=discord)](https://discord.gg/t8nHSvA) +[![Code style](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) + +Parse TF2 items to SKU format with Python. + +## Donate +- BTC: `bc1qntlxs7v76j0zpgkwm62f6z0spsvyezhcmsp0z2` +- [Steam Trade Offer](https://steamcommunity.com/tradeoffer/new/?partner=293059984&token=0-l_idZR) + +## Usage +```python +>>> from tf2_sku import to_sku, from_sku + +>>> to_sku({"defindex": 5021, "quality": 6}) +"5021;6" +# https://marketplace.tf/items/tf2/5021;6 + +>>> from_sku("161;3;kt-3") +{ + "defindex": 161, + "quality": 3, + "effect": -1, + "australium": False, + "craftable": True, + "wear": -1, + "skin": -1, + "strange": -1, + "killstreak_tier": 3, + "target_defindex": -1, + "festivized": False, + "craft_number": -1, + "crate_number": -1, + "output_defindex": -1, + "output_quality": -1, + "paint": -1, +} +# https://marketplace.tf/items/tf2/161;3;kt-3 + +>>> to_sku({ +... "defindex": 199, +... "quality": 5, +... "effect": 702, +... "wear": 3, +... "skin": 292, +... "strange": True, +... "killstreak_tier": 3}) +"199;5;u702;w3;pk292;strange;kt-3" +# https://marketplace.tf/items/tf2/199;5;u702;w3;pk292;strange;kt-3 +``` + +## Setup +### Install +```bash +pip install tf2-sku +# or +python -m pip install tf2-sku +``` + +### Upgrade +```bash +pip upgrade tf2-sku +# or +python -m pip upgrade tf2-sku +``` + +## Testing +```bash +# tf2-sku/ +python -m unittest +``` diff --git a/README.rst b/README.rst deleted file mode 100644 index f299cec..0000000 --- a/README.rst +++ /dev/null @@ -1,121 +0,0 @@ -tf2_sku -======= -|pypi| |license| |stars| |issues| |repo_size| |chat| - -|donate_steam| |donate| - -Format items as strings or objects using Python 3. -Python port of `node-tf2-sku`_. - -.. _node-tf2-sku: https://github.com/Nicklason/node-tf2-sku - -.. contents:: Table of Contents - :depth: 1 - -Installing ----------- -Install and update using `pip`_: - -.. code-block:: text - - pip install tf2-sku - -.. _pip: https://pip.pypa.io/en/stable/quickstart/ - -Usage ------ - -.. code-block:: python - - from tf2_sku import to_sku, from_sku - - # To SKU - # Mann Co. Supply Crate Key - item = { - 'defindex': 5021, - 'quality': 6, - 'craftable': True, - 'killstreak': 0, - 'australium': False, - 'festive': False, - 'effect': None, - 'paintkit': None, - 'wear': None, - 'quality2': None, - 'target': None, - 'craftnumber': None, - 'crateseries': None, - 'output': None, - 'outputQuality': None - } - - print(to_sku(item)) - # '5021;6' - - - # From SKU - # Mann Co. Supply Crate Key - sku = '5021;6' - - print(from_sku(sku)) - # {'defindex': 5021, 'quality': 6, 'craftable': True, 'killstreak': 0, 'australium': False, - # 'festive': False, 'effect': None, 'paintkit': None, 'wear': None, 'quality2': None, 'target': None, - # 'craftnumber': None, 'crateseries': None, 'output': None, 'outputQuality': None} - - -License -------- -Copyright (c) 2020 `offish`_ - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - -.. _offish: https://offi.sh - - -.. |pypi| image:: https://img.shields.io/pypi/v/tf2_sku.svg - :target: https://pypi.org/project/tf2_sku - :alt: Latest version released on PyPi - -.. |license| image:: https://img.shields.io/github/license/offish/tf2_sku.svg - :target: https://github.com/offish/tf2_sku/blob/master/LICENSE - :alt: License - -.. |stars| image:: https://img.shields.io/github/stars/offish/tf2_sku.svg - :target: https://github.com/offish/tf2_sku/stargazers - :alt: Stars - -.. |issues| image:: https://img.shields.io/github/issues/offish/tf2_sku.svg - :target: https://github.com/offish/tf2_sku/issues - :alt: Issues - -.. |repo_size| image:: https://img.shields.io/github/repo-size/offish/tf2_sku.svg - :target: https://github.com/offish/tf2_sku - :alt: Repo Size - -.. |chat| image:: https://img.shields.io/discord/467040686982692865.svg - :target: https://discord.gg/t8nHSvA - :alt: Discord - -.. |donate_steam| image:: https://img.shields.io/badge/donate-steam-green.svg - :target: https://steamcommunity.com/tradeoffer/new/?partner=293059984&token=0-l_idZR - :alt: Donate via Steam - -.. |donate| image:: https://img.shields.io/badge/donate-paypal-blue.svg - :target: https://www.paypal.me/0ffish - :alt: Donate via PayPal diff --git a/example.py b/example.py deleted file mode 100644 index e3bcaa3..0000000 --- a/example.py +++ /dev/null @@ -1,34 +0,0 @@ -from tf2_sku import to_sku, from_sku - -# To SKU -# Mann Co. Supply Crate Key -item = { - 'defindex': 5021, - 'quality': 6, - 'craftable': True, - 'killstreak': 0, - 'australium': False, - 'festive': False, - 'effect': None, - 'paintkit': None, - 'wear': None, - 'quality2': None, - 'target': None, - 'craftnumber': None, - 'crateseries': None, - 'output': None, - 'outputQuality': None -} - -print(to_sku(item)) -# '5021;6' - - -# From SKU -# Mann Co. Supply Crate Key -sku = '5021;6' - -print(from_sku(sku)) - # {'defindex': 5021, 'quality': 6, 'craftable': True, 'killstreak': 0, 'australium': False, - # 'festive': False, 'effect': None, 'paintkit': None, 'wear': None, 'quality2': None, 'target': None, - # 'craftnumber': None, 'crateseries': None, 'output': None, 'outputQuality': None} diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..abdb893 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,26 @@ +[build-system] +requires = ["setuptools>=45", "setuptools_scm[toml]>=6.2"] +build-backend = "setuptools.build_meta" + +[project] +name = "tf2-sku" +authors = [ + { name="offish", email="overutilization@gmail.com" }, +] +description = "Parse TF2 items to SKU format" +readme = "README.md" +requires-python = ">=3.7" +keywords = ["tf2", "sku"] +classifiers = [ + "Programming Language :: Python :: 3", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", +] +dependencies = [] +dynamic = ["version"] + +[project.urls] +"Homepage" = "https://github.com/offish/tf2-sku" +"Bug Tracker" = "https://github.com/offish/tf2-sku/issues" + +[tool.setuptools_scm] \ No newline at end of file diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index fc4a79f..0000000 --- a/setup.cfg +++ /dev/null @@ -1,2 +0,0 @@ -[metadata] -description-file = README.rst \ No newline at end of file diff --git a/setup.py b/setup.py index 09232ca..b26e383 100644 --- a/setup.py +++ b/setup.py @@ -1,32 +1,3 @@ from setuptools import setup -import re - -with open("README.rst", "r") as f: - long_description = f.read() - - -with open("tf2_sku/__init__.py") as f: - version = re.search( - r"""^__version__\s*=\s*['"]([^\'"]*)['"]""", f.read(), re.MULTILINE).group(1) - - -setup( - name="tf2_sku", - version=version, - author="offish", - author_email="overutilization@gmail.com", - description="Format TF2 items as strings or objects.", - long_description=long_description, - long_description_content_type="text/markdown", - license="MIT", - url="https://github.com/offish/tf2_sku", - download_url="https://github.com/offish/tf2_sku/tarball/v" + version, - packages=["tf2_sku"], - classifiers=[ - "Programming Language :: Python :: 3", - "License :: OSI Approved :: MIT License", - "Operating System :: OS Independent", - ], - python_requires='>=3.6', -) +setup() diff --git a/tf2_sku/__init__.py b/src/tf2_sku/__init__.py similarity index 57% rename from tf2_sku/__init__.py rename to src/tf2_sku/__init__.py index 8caeba0..77d6852 100644 --- a/tf2_sku/__init__.py +++ b/src/tf2_sku/__init__.py @@ -1,15 +1,12 @@ """ -tf2_sku -======= +tf2-sku Format TF2 items as strings or objects. """ - -__title__ = "tf2_sku" +__title__ = "tf2-sku" __author__ = "offish" __license__ = "MIT" -__version__ = "1.0.1" - +__version__ = "2.0.0" from .sku import to_sku, from_sku -from .prettify import prettify +from .utils import SKU, MAPPING diff --git a/src/tf2_sku/sku.py b/src/tf2_sku/sku.py new file mode 100644 index 0000000..a952291 --- /dev/null +++ b/src/tf2_sku/sku.py @@ -0,0 +1,75 @@ +from .utils import SKU, MAPPING + + +def to_sku(item: dict) -> str: + """Takes an item dictionary and formats it to a SKU. + + :param item: Item dictionary containing specific keys and values + :type item: dict + + :return: SKU string + :rtype: str + + Example: + to_sku({ + "defindex": 199, + "quality": 5, + "effect": 702, + "australium": False, + "craftable": True, + "wear": 3, + "skin": 292, + "strange": True, + "killstreak_tier": 3, + "target_defindex": -1, + "festivized": False, + "craft_number": -1, + "crate_number": -1, + "output_defindex": -1, + "output_quality": -1, + "paint": -1, + }) + """ + sku = SKU(**item) + return str(sku) + + +def from_sku(sku: str) -> dict: + """Takes a SKU and formats it to an item dictionary. + + :param sku: SKU string + :type sku: str + + :return: Item dictionary containing specific keys and values + :rtype: dict + + Example: + from_sku("199;5;u702;w3;pk292;strange;kt-3") + """ + parts = sku.split(";") + item = {"defindex": int(parts[0]), "quality": int(parts[1])} + + for part in parts[2:]: + for key in MAPPING: + default = MAPPING[key].format("") + + if not default in part: + continue + + value = part.replace(default, "") + + if key == "craftable": + value = False + + else: + if not value: + value = True + + else: + value = int(value) + + item[key] = value + break + + sku = SKU(**item) + return sku.__dict__ diff --git a/src/tf2_sku/utils.py b/src/tf2_sku/utils.py new file mode 100644 index 0000000..08c33af --- /dev/null +++ b/src/tf2_sku/utils.py @@ -0,0 +1,57 @@ +from dataclasses import dataclass, fields + + +MAPPING = { + "effect": "u{}", + "australium": "australium", + "craftable": "uncraftable", + "wear": "w{}", + "skin": "pk{}", + "strange": "strange", + "killstreak_tier": "kt-{}", + "target_defindex": "td-{}", + "festivized": "festive", + "craft_number": "n{}", + "crate_number": "c{}", + "output_defindex": "od-{}", + "output_quality": "oq-{}", + "paint": "p", + # "sheen": "ks-{}", + # "killstreaker": "ke-{}", +} + + +@dataclass +class SKU: + defindex: int + quality: int + effect: int = -1 + australium: bool = False + craftable: bool = True + wear: int = -1 + skin: int = -1 + strange: bool = False + killstreak_tier: int = -1 + target_defindex: int = -1 + festivized: bool = False + craft_number: int = -1 + crate_number: int = -1 + output_defindex: int = -1 + output_quality: int = -1 + paint: int = -1 + # sheen: int = -1 + # killstreaker: int = -1 + + def __str__(self) -> str: + sku = f"{self.defindex};{self.quality}" + + for field in fields(self): + if field.name in ["defindex", "quality"]: + continue + + value = getattr(self, field.name) + + if value != field.default: + sku += ";" + MAPPING[field.name].format(value) + + return sku diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_sku.py b/tests/test_sku.py new file mode 100644 index 0000000..9f36443 --- /dev/null +++ b/tests/test_sku.py @@ -0,0 +1,67 @@ +from src.tf2_sku import to_sku, from_sku + +from unittest import TestCase + + +MANN_CO_KEY = { + "defindex": 5021, + "quality": 6, + "effect": -1, + "australium": False, + "craftable": True, + "wear": -1, + "skin": -1, + "strange": False, + "killstreak_tier": -1, + "target_defindex": -1, + "festivized": False, + "craft_number": -1, + "crate_number": -1, + "output_defindex": -1, + "output_quality": -1, + "paint": -1, +} + + +class TestSKU(TestCase): + def test_key(self): + sku = "5021;6" + item = {"defindex": 5021, "quality": 6} + result = to_sku(item) + + item_dict = from_sku(sku) + + skued = to_sku(MANN_CO_KEY) + + self.assertEqual(result, sku) + self.assertEqual(item_dict, MANN_CO_KEY) + self.assertEqual(sku, skued) + + def test_from_sku(self): + sku = "5021;6" + item = from_sku(sku) + + self.assertEqual(item, MANN_CO_KEY) + + def test_ellis_cap_double(self): + item = {"defindex": 236, "quality": 6} + sku = to_sku(item) + + item_again = from_sku(sku) + sku_again = to_sku(item_again) + + self.assertEqual(sku, sku_again) + + def test_skin(self): + original = "199;5;u702;w3;pk292;strange;kt-3" + item = from_sku(original) + sku = to_sku(item) + + self.assertEqual(sku, original) + + def test_types(self): + item_dict = from_sku("5021;6") + sku = to_sku(item_dict) + + self.assertTrue(isinstance(item_dict, dict)) + self.assertTrue(isinstance(sku, str)) diff --git a/tf2_sku/prettify.py b/tf2_sku/prettify.py deleted file mode 100644 index 660452a..0000000 --- a/tf2_sku/prettify.py +++ /dev/null @@ -1,38 +0,0 @@ - - -def prettify(target, template): - """Sort properties in an object using a template - """ - if target == None or template == None: - raise KeyError('Missing arguments: target, template, or both') - - elif not (isinstance(target, dict) or isinstance(target, list)): - raise TypeError('Invalid argument: first argument (target) must be an object') - - elif not (isinstance(template, dict) or isinstance(template, list)): - raise TypeError('Invalid argument: second argument (template) must be either an object or array') - - prettified = {} - - for key in template: - - if key not in template: - continue - - elif isinstance(template, list): - key = template[key] - - - if not isinstance(key, str): - raise TypeError('Invalid argument: template list must only contain strings') - - try: - value = target[key] - - if value is not None: - prettified[key] = value - - except KeyError: - prettified[key] = template[key] - - return prettified diff --git a/tf2_sku/sku.py b/tf2_sku/sku.py deleted file mode 100644 index 6eca091..0000000 --- a/tf2_sku/sku.py +++ /dev/null @@ -1,132 +0,0 @@ -from .prettify import prettify - - -TEMPLATE = { - 'defindex': 0, - 'quality': 0, - 'craftable': True, - 'killstreak': 0, - 'australium': False, - 'festive': False, - 'effect': None, - 'paintkit': None, - 'wear': None, - 'quality2': None, - 'target': None, - 'craftnumber': None, - 'crateseries': None, - 'output': None, - 'outputQuality': None -} - - -def to_sku(item): - """Convert SKU to item object - """ - item = prettify(item, TEMPLATE) - - sku = f'{item["defindex"]};{item["quality"]}' - - if 'craftable' in item and not item['craftable']: - sku += ';uncraftable' - - if 'effect' in item and item['effect']: - sku += f';u{item["effect"]}' - - if 'australium' in item and item['australium']: - sku += ';australium' - - if 'wear' in item and item['wear']: - sku += f';w{item["wear"]}' - - if 'paintkit' in item and item['paintkit']: - sku += f';pk{item["paintkit"]}' - - if 'quality2' in item and item['quality2'] == 11: - sku += ';strange' - - if isinstance(item['killstreak'], int) and not item['killstreak'] == 0: - sku += f';kt-{item["killstreak"]}' - - if 'target' in item and item['target']: - sku += f';td-{item["target"]}' - - if 'festive' in item and item['festive']: - sku += ';festive' - - if 'craftnumber' in item and item['craftnumber']: - sku += f';n${item["craftnumber"]}' - - if 'crateseries' in item and item['crateseries']: - sku += f';c{item["crateseries"]}' - - if 'output' in item and item['output']: - sku += f';od-{item["output"]}' - - if 'outputQuality' in item and item['outputQuality']: - sku += f';oq-{item["outputQuality"]}' - - return sku - - -def from_sku(sku): - """Convert item object to SKU - """ - attributes = {} - parts = sku.split(';') - - if len(parts) > 0: - if parts[0].isnumeric(): - attributes['defindex'] = int(parts[0]) - parts.pop(0) - - if len(parts) > 0: - if parts[0].isnumeric(): - attributes['quality'] = int(parts[0]) - parts.pop(0) - - for part in parts: - attribute = part.replace('-', '') - - if attribute == 'uncraftable': - attributes['craftable'] = False - - elif attribute == 'australium': - attributes['australium'] = True - - elif attribute == 'festive': - attributes['festive'] = True - - elif attribute == 'strange': - attributes['quality2'] = 11 - - elif attribute.startswith('kt') and attribute[2:].isnumeric(): - attributes['killstreak'] = int(attribute[2:]) - - elif attribute.startswith('u') and attribute[1:].isnumeric(): - attributes['effect'] = int(attribute[1:]) - - elif attribute.startswith('pk') and attribute[2:].isnumeric(): - attributes['paintkit'] = int(attribute[2:]) - - elif attribute.startswith('w') and attribute[1:].isnumeric(): - attributes['wear'] = int(attribute[1:]) - - elif attribute.startswith('td') and attribute[2:].isnumeric(): - attributes['target'] = int(attribute[2:]) - - elif attribute.startswith('n') and attribute[1:].isnumeric(): - attributes['craftnumber'] = int(attribute[1:]) - - elif attribute.startswith('c') and attribute[1:].isnumeric(): - attributes['crateseries'] = int(attribute[1:]) - - elif attribute.startswith('od') and attribute[2:].isnumeric(): - attributes['output'] = int(attribute[2:]) - - elif attribute.startswith('oq') and attribute[2:].isnumeric(): - attributes['outputQuality'] = int(attribute[2:]) - - item = prettify(attributes, TEMPLATE) - - return item