From 5db7737c26b6c1d3560333e9950bab5a615b5baa Mon Sep 17 00:00:00 2001 From: Rossen Georgiev Date: Tue, 24 Nov 2015 20:41:01 +0200 Subject: [PATCH] move steam2/3 coversions to seperate functions --- steam/steamid.py | 173 ++++++++++++++++++++++-------------------- tests/test_steamid.py | 154 +++++++++++++++++++++++-------------- 2 files changed, 189 insertions(+), 138 deletions(-) diff --git a/steam/steamid.py b/steam/steamid.py index 60868da..0a71123 100644 --- a/steam/steamid.py +++ b/steam/steamid.py @@ -1,27 +1,27 @@ import re from steam.enums import EType, EUniverse +ETypeCharMap = { + 0: 'I', + 1: 'U', + 2: 'M', + 3: 'G', + 4: 'A', + 5: 'P', + 6: 'C', + 7: 'g', + 8: 'T', + 8: 'c', + 8: 'L', + 10: 'a', +} + class SteamID(object): """ Object for converting steamID to its' various representations """ - ETypeChar = { - 0: 'I', - 1: 'U', - 2: 'M', - 3: 'G', - 4: 'A', - 5: 'P', - 6: 'C', - 7: 'g', - 8: 'T', - 8: 'c', - 8: 'L', - 10: 'a', - } - def __init__(self, *args, **kwargs): """ The instance can be initialized with various parameters @@ -41,36 +41,26 @@ class SteamID(object): # you should use the WebAPI to resolve them reliably SteamID('https://steamcommunity.com/id/drunkenf00l') """ + self.id = 0 + self.type = EType.Invalid + self.universe = EUniverse.Invalid + self.instance = 0 - largs = len(args) - - if largs == 0 and len(kwargs) == 0: - self.id = 0 - self.type = EType.Invalid - self.universe = EUniverse.Invalid - self.instance = 0 - elif largs > 0: - if largs > 1: - raise ValueError("Needed only 1 arg, got %d" % largs) - + if len(args) == 1: value = str(args[0]) # numeric input - if value.isdigit() or (value.startswith('-') and value[1:].isdigit()): + if value.isdigit(): value = int(value) - if 0 > value: - raise ValueError("Expected positive int, got %d" % value) - if value >= 2**64: - raise ValueError("Expected a 32/64 bit int") # 32 bit account id - if value < 2**32: + if 0 < value < 2**32: self.id = value self.type = EType.Individual self.universe = EUniverse.Public self.instance = 1 # 64 bit - else: + elif value < 2**64: self.id = value & 0xFFffFFff self.instance = (value >> 32) & 0xFFffF self.type = EType((value >> 52) & 0xF) @@ -78,61 +68,25 @@ class SteamID(object): # textual input e.g. [g:1:4] else: - # try steam2 - match = re.match(r"^STEAM_(?P[01])" - r":(?P[0-1])" - r":(?P\d+)$", value - ) - - if match: - self.id = (int(match.group('id')) << 1) | int(match.group('reminder')) - self.universe = EUniverse(1) - self.type = EType(1) - self.instance = 1 - return - - # try steam3 - typeChars = ''.join(self.ETypeChar.values()) - match = re.match(r"^\[" - r"(?P[%s]):" # type char - r"(?P\d+):" # universe - r"(?P\d+)" # accountid - r"(:(?P\d+))?" # instance - r"\]$" % typeChars, value - ) - if match: - self.id = int(match.group('id')) - self.universe = EUniverse(int(match.group('universe'))) - inverseETypeChar = dict((b, a) for (a, b) in self.ETypeChar.items()) - self.type = EType(inverseETypeChar[match.group('type')]) - self.instance = match.group('instance') - if self.instance is None: - if self.type in (EType.Individual, EType.GameServer): - self.instance = 1 - else: - self.instance = 0 - else: - self.instance = int(self.instance) - return - - raise ValueError("Expected a steam32/64 or textual steam id" - ", got %s" % repr(value) - ) + result = steam2_to_tuple(value) or steam3_to_tuple(value) - elif len(kwargs): - if 'id' not in kwargs: - raise ValueError("Expected at least 'id' kwarg") + if result: + (self.id, + self.type, + self.universe, + self.instance + ) = result - self.id = int(kwargs['id']) - assert self.id <= 0xffffFFFF, "id larger than 32bits" + elif len(kwargs): + self.id = int(kwargs.get('id', 0)) - value = kwargs.get('type', 0) + value = kwargs.get('type', 1) if type(value) in (int, EType): self.type = EType(value) else: self.type = EType[value.lower().capitalize()] - value = kwargs.get('universe', 0) + value = kwargs.get('universe', 1) if type(value) in (int, EUniverse): self.universe = EUniverse(value) else: @@ -160,11 +114,20 @@ class SteamID(object): return str(self.as_64) def __cmp__(self, other): - return cmp(self.as_64, other.as_64) + if isinstance(other, SteamID): + return cmp(self.as_64, other.as_64) + else: + raise RuntimeError("Can only compare SteamID instances") def __hash__(self): return hash(self.as_64) + def is_valid(self): + return (0 < self.id < 2**32 + and self.type is not EType.Invalid + and self.universe is not EUniverse.Invalid + ) + @property def as_steam2(self): return "STEAM_0:%s:%s" % ( @@ -176,14 +139,14 @@ class SteamID(object): def as_steam3(self): if self.type is EType.AnonGameServer: return "[%s:%s:%s:%s]" % ( - self.ETypeChar[self.type.value], + ETypeCharMap[self.type.value], self.universe.value, self.id, self.instance ) else: return "[%s:%s:%s]" % ( - self.ETypeChar[self.type.value], + ETypeCharMap[self.type.value], self.universe.value, self.id, ) @@ -212,6 +175,50 @@ class SteamID(object): return None +def steam2_to_tuple(value): + match = re.match(r"^STEAM_(?P[01])" + r":(?P[0-1])" + r":(?P\d+)$", value + ) + + if not match: + return None + + steam32 = (int(match.group('id')) << 1) | int(match.group('reminder')) + + return (steam32, EType(1), EUniverse(1), 1) + +def steam3_to_tuple(value): + typeChars = ''.join(ETypeCharMap.values()) + match = re.match(r"^\[" + r"(?P[%s]):" # type char + r"(?P\d+):" # universe + r"(?P\d+)" # accountid + r"(:(?P\d+))?" # instance + r"\]$" % typeChars, value + ) + if not match: + return None + + steam32 = int(match.group('id')) + + universe = EUniverse(int(match.group('universe'))) + + inverseETypeCharMap = dict((b, a) for (a, b) in ETypeCharMap.items()) + etype = EType(inverseETypeCharMap[match.group('type')]) + + instance = match.group('instance') + + if instance is None: + if etype in (EType.Individual, EType.GameServer): + instance = 1 + else: + instance = 0 + else: + instance = int(instance) + + return (steam32, etype, universe, instance) + def steam64_from_url(url): """ Takes a Steam Community url and returns steam64 or None diff --git a/tests/test_steamid.py b/tests/test_steamid.py index 04a3687..19982a7 100644 --- a/tests/test_steamid.py +++ b/tests/test_steamid.py @@ -3,7 +3,7 @@ import mock import vcr from steam import steamid -from steam.steamid import SteamID +from steam.steamid import SteamID, ETypeCharMap from steam.enums import EType, EUniverse @@ -14,27 +14,40 @@ class SteamID_initialization(unittest.TestCase): self.assertEqual(obj.universe, test_list[2]) self.assertEqual(obj.instance, test_list[3]) - def test_arg_toomany(self): - with self.assertRaises(ValueError): - SteamID(1, 2) - with self.assertRaises(ValueError): - SteamID(1, 2, 3) - with self.assertRaises(ValueError): - SteamID(1, 2, 3, 4) + def test_hash(self): + self.assertEqual(hash(SteamID(1)), hash(SteamID(1))) + self.assertNotEqual(hash(SteamID(12345)), hash(SteamID(8888))) + + def test_cmp(self): + self.assertEqual(SteamID(1), SteamID(1)) + self.assertTrue(SteamID(2) > SteamID(1)) + self.assertTrue(SteamID(2) < SteamID(4)) + + with self.assertRaises(RuntimeError): + a = SteamID(5) == 5 + b = SteamID(5) == '5' + + def test_is_valid(self): + self.assertTrue(SteamID(1).is_valid()) + self.assertTrue(SteamID(id=5).is_valid()) + + self.assertFalse(SteamID(0).is_valid()) + self.assertFalse(SteamID(-50).is_valid()) + + self.assertFalse(SteamID(id=1, type=EType.Invalid).is_valid()) + self.assertFalse(SteamID(id=1, universe=EUniverse.Invalid).is_valid()) + + def test_arg_toomany_invalid(self): + self.compare(SteamID(1, 2), + [0, EType.Invalid, EUniverse.Invalid, 0]) + self.compare(SteamID(1, 2, 3), + [0, EType.Invalid, EUniverse.Invalid, 0]) + self.compare(SteamID(1, 2, 3, 4), + [0, EType.Invalid, EUniverse.Invalid, 0]) ###################################################### # 1 ARG ###################################################### - def test_arg_number_out_of_range(self): - self.assertRaises(ValueError, SteamID, -1) - self.assertRaises(ValueError, SteamID, '-1') - self.assertRaises(ValueError, SteamID, -5555555) - self.assertRaises(ValueError, SteamID, '-5555555') - self.assertRaises(ValueError, SteamID, 2**64) - self.assertRaises(ValueError, SteamID, str(2**64)) - self.assertRaises(ValueError, SteamID, 2**128) - self.assertRaises(ValueError, SteamID, str(2**128)) - def test_arg_steam32(self): self.compare(SteamID(1), [1, EType.Individual, EUniverse.Public, 1]) @@ -74,50 +87,46 @@ class SteamID_initialization(unittest.TestCase): ###################################################### # 1 arg - steam2/steam3 format ###################################################### - def test_arg_text_invalid(self): - with self.assertRaises(ValueError): - SteamID("randomtext") + @mock.patch.multiple('steam.steamid', + steam2_to_tuple=mock.DEFAULT, + steam3_to_tuple=mock.DEFAULT, + ) + def test_arg_steam2(self, steam2_to_tuple, steam3_to_tuple): + steam2_to_tuple.return_value = (1, 2, 3, 4) + steam3_to_tuple.return_value = (5, 6, 7, 8) - def test_arg_steam2(self): - self.compare(SteamID("STEAM_0:1:1"), - [3, EType.Individual, EUniverse.Public, 1] - ) - self.compare(SteamID("STEAM_1:1:1"), - [3, EType.Individual, EUniverse.Public, 1] - ) - self.compare(SteamID("STEAM_0:0:4"), - [8, EType.Individual, EUniverse.Public, 1] - ) - self.compare(SteamID("STEAM_1:0:4"), - [8, EType.Individual, EUniverse.Public, 1] - ) + test_instance = SteamID('STEAM_1:1:1') - def test_arg_steam3(self): - self.compare(SteamID("[U:1:1234]"), - [1234, EType.Individual, EUniverse.Public, 1] - ) - self.compare(SteamID("[G:1:1234]"), - [1234, EType.GameServer, EUniverse.Public, 1] - ) - self.compare(SteamID("[g:1:4]"), - [4, EType.Clan, EUniverse.Public, 0] - ) - self.compare(SteamID("[A:1:4]"), - [4, EType.AnonGameServer, EUniverse.Public, 0] - ) - self.compare(SteamID("[A:1:1234:567]"), - [1234, EType.AnonGameServer, EUniverse.Public, 567] - ) + steam2_to_tuple.assert_called_once_with('STEAM_1:1:1') + self.assertFalse(steam3_to_tuple.called) + + self.compare(test_instance, + [1, 2, 3, 4]) + + @mock.patch.multiple('steam.steamid', + steam2_to_tuple=mock.DEFAULT, + steam3_to_tuple=mock.DEFAULT, + ) + def test_arg_steam3(self, steam2_to_tuple, steam3_to_tuple): + steam2_to_tuple.return_value = None + steam3_to_tuple.return_value = (5, 6, 7, 8) + + test_instance = SteamID('[g:1:4]') + + steam2_to_tuple.assert_called_once_with('[g:1:4]') + steam3_to_tuple.assert_called_once_with('[g:1:4]') + + self.compare(test_instance, + [5, 6, 7, 8]) + + def test_arg_text_invalid(self): + self.compare(SteamID("invalid_format"), + [0, EType.Invalid, EUniverse.Invalid, 0]) ###################################################### # KWARGS ###################################################### def test_kwarg_id(self): - # id kwarg is required always - with self.assertRaises(ValueError): - SteamID(instance=0) - SteamID(id=None) - self.assertEqual(SteamID(id=555).id, 555) self.assertEqual(SteamID(id='555').id, 555) @@ -254,3 +263,38 @@ class steamid_functions(unittest.TestCase): sid = steamid.steam64_from_url('https://steamcommunity.com/groups/Valve') self.assertEqual(sid, '103582791429521412') + def test_arg_steam2(self): + self.assertIsNone(steamid.steam2_to_tuple('invalid_format')) + + self.assertEqual(steamid.steam2_to_tuple("STEAM_0:1:1"), + (3, EType.Individual, EUniverse.Public, 1) + ) + self.assertEqual(steamid.steam2_to_tuple("STEAM_1:1:1"), + (3, EType.Individual, EUniverse.Public, 1) + ) + self.assertEqual(steamid.steam2_to_tuple("STEAM_0:0:4"), + (8, EType.Individual, EUniverse.Public, 1) + ) + self.assertEqual(steamid.steam2_to_tuple("STEAM_1:0:4"), + (8, EType.Individual, EUniverse.Public, 1) + ) + + def test_arg_steam3(self): + self.assertIsNone(steamid.steam3_to_tuple('invalid_format')) + + self.assertEqual(steamid.steam3_to_tuple("[U:1:1234]"), + (1234, EType.Individual, EUniverse.Public, 1) + ) + self.assertEqual(steamid.steam3_to_tuple("[G:1:1234]"), + (1234, EType.GameServer, EUniverse.Public, 1) + ) + self.assertEqual(steamid.steam3_to_tuple("[g:1:4]"), + (4, EType.Clan, EUniverse.Public, 0) + ) + self.assertEqual(steamid.steam3_to_tuple("[A:1:4]"), + (4, EType.AnonGameServer, EUniverse.Public, 0) + ) + self.assertEqual(steamid.steam3_to_tuple("[A:1:1234:567]"), + (1234, EType.AnonGameServer, EUniverse.Public, 567) + ) +