From cb4428abed2da280c445b13da6ebcb3935e8defc Mon Sep 17 00:00:00 2001 From: Philip Cammarata Date: Wed, 4 Feb 2015 20:38:41 +0900 Subject: [PATCH 1/9] Update setup.py --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 954d9f6..4ab6250 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ import sys import textwrap -from setuptools import setup +from setuptools import setup, find_packages from setuptools.command.test import test as TestCommand @@ -35,7 +35,7 @@ def run_tests(self): "various parts of Steam's public interfaces"), author="Oliver Ainsworth", author_email="ottajay@googlemail.com", - packages=["valve"], + packages=find_packages(), install_requires=[ "six>=1.6", "requests>=2.0", From 2af1a6be5bed18cb8cac629b4ca5da52df495c36 Mon Sep 17 00:00:00 2001 From: Dylan Grafmyre Date: Wed, 11 Feb 2015 23:34:22 -0800 Subject: [PATCH 2/9] pylint top level module --- valve/__init__.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/valve/__init__.py b/valve/__init__.py index 1101ddf..8e7762f 100644 --- a/valve/__init__.py +++ b/valve/__init__.py @@ -1,5 +1,11 @@ # -*- coding: utf-8 -*- # Copyright (C) 2013 Oliver Ainsworth -from __future__ import (absolute_import, - unicode_literals, print_function, division) +""" + Vavle services interfaceq +""" + +from __future__ import absolute_import +from __future__ import unicode_literals +from __future__ import print_function +from __future__ import division From 99547f8fea6026c51ede75f4f521d20dd1b9fe3e Mon Sep 17 00:00:00 2001 From: Dylan Grafmyre Date: Wed, 11 Feb 2015 23:40:10 -0800 Subject: [PATCH 3/9] Pylint for vdf.py --- valve/vdf.py | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/valve/vdf.py b/valve/vdf.py index 558e7ac..6d1bb43 100644 --- a/valve/vdf.py +++ b/valve/vdf.py @@ -24,6 +24,8 @@ UNQUOTED = 1 NEVER = 2 +CURRENT = -1 +PREVIOUS = -2 def coerce_type(token): """ @@ -52,6 +54,7 @@ def coerce_type(token): # Largely based on necavi's https://github.com/necavi/py-keyvalues +# TODO: reduce complexity def loads(src, encoding=None, coerce_=UNQUOTED): """ Loades a VDF string into a series of nested dictionaries. @@ -203,15 +206,13 @@ def loads(src, encoding=None, coerce_=UNQUOTED): exc.message, src[i], line, col)) dict_ = {} dict_stack = [dict_] - CURRENT = -1 - PREVIOUS = -2 - for type, key, value, should_coerce in pairs[1:]: - if type == _KV_BLOCK: + for my_type, key, value, should_coerce in pairs[1:]: + if my_type == _KV_BLOCK: dict_stack.append({}) dict_stack[PREVIOUS][key] = dict_stack[CURRENT] - elif type == _KV_BLOCKEND: + elif my_type == _KV_BLOCKEND: dict_stack = dict_stack[:CURRENT] - elif type == _KV_PAIR: + elif my_type == _KV_PAIR: dict_stack[CURRENT][key] = (coerce_type(value) if should_coerce else value) # else: @@ -220,14 +221,14 @@ def loads(src, encoding=None, coerce_=UNQUOTED): return dict_ -def load(fp, encoding=None, coerce_=UNQUOTED): +def load(file_handle, encoding=None, coerce_=UNQUOTED): """ Same as loads but takes a file-like object as the source. """ - return loads(fp.read(), encoding, coerce_) + return loads(file_handle.read(), encoding, coerce_) -def dumps(obj, encoding=None, indent=u" ", object_encoders={}): +def dumps(obj, encoding=None, indent=u" ", object_encoders=None): """ Serialises a series of nested dictionaries to the VDF/KeyValues format and returns it as a string. @@ -247,6 +248,9 @@ def dumps(obj, encoding=None, indent=u" ", object_encoders={}): textual representaiton may also be 'wrong.' """ + if not object_encoders: + object_encoders = dict() + object_codecs = { float: lambda v: unicode(repr(v / 1.0)), } @@ -256,6 +260,8 @@ def dumps(obj, encoding=None, indent=u" ", object_encoders={}): lines = [] def recurse_obj(obj, indent_level=0): + """Recursive object + """ ind = indent * indent_level for key, value in obj.iteritems(): if isinstance(value, dict): @@ -278,10 +284,12 @@ def recurse_obj(obj, indent_level=0): return u"\n".join(lines) -def dump(obj, fp, encoding, indent=u" ", object_encoders={}): +def dump(obj, file_handle, encoding, indent=u" ", object_encoders=None): """ - Same as dumps but takes a file-like object 'fp' which will be + Same as dumps but takes a file-like object 'file_handle' which will be written to. """ + if not object_encoders: + object_encoders = dict() - return fp.write(dumps(obj, encoding, indent, object_encoders)) + return file_handle.write(dumps(obj, encoding, indent, object_encoders)) From da3c9d5b778fcb38b821ee7c494523b4702a5086 Mon Sep 17 00:00:00 2001 From: Dylan Grafmyre Date: Wed, 11 Feb 2015 23:41:39 -0800 Subject: [PATCH 4/9] Pylint for util.py --- valve/source/util.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/valve/source/util.py b/valve/source/util.py index 1081ba9..0f5d937 100644 --- a/valve/source/util.py +++ b/valve/source/util.py @@ -1,6 +1,10 @@ # -*- coding: utf-8 -*- # Copyright (C) 2014 Oliver Ainsworth +""" + Utillity Classes +""" + from __future__ import (absolute_import, unicode_literals, print_function, division) @@ -228,6 +232,8 @@ def __eq__(self, other): @property def char(self): + """Calls `chr` on object's value. + """ return chr(self.value) From 6dc339f9ae746c14acb999a2435756a7f416d73f Mon Sep 17 00:00:00 2001 From: Dylan Grafmyre Date: Wed, 11 Feb 2015 23:45:14 -0800 Subject: [PATCH 5/9] Pylint for master_server.py --- valve/source/master_server.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/valve/source/master_server.py b/valve/source/master_server.py index c29acb9..520ef6d 100644 --- a/valve/source/master_server.py +++ b/valve/source/master_server.py @@ -1,6 +1,10 @@ # -*- coding: utf-8 -*- # Copyright (C) 2013-2014 Oliver Ainsworth +""" + Tools for working with the Valve master server services. +""" + from __future__ import (absolute_import, unicode_literals, print_function, division) @@ -76,7 +80,8 @@ def _query(self, region, filter_string): if not address.is_null: yield address["host"], address["port"] - def _map_region(self, region): + @staticmethod + def _map_region(region): """Convert string to numeric region identifier If given a non-string then a check is performed to ensure it is a @@ -125,6 +130,7 @@ def _map_region(self, region): raise ValueError("Invalid region identifier {!r}".format(reg)) return regions + # TODO: Reduce comlexity. def find(self, region="all", **filters): """Find servers for a particular region and set of filtering rules @@ -228,11 +234,11 @@ def find(self, region="all", **filters): individually. See :mod:`valve.source.a2s`. """ if isinstance(region, (int, six.text_type)): - regions = self._map_region(region) + regions = MasterServerQuerier._map_region(region) else: regions = [] for reg in region: - regions.extend(self._map_region(reg)) + regions.extend(MasterServerQuerier._map_region(reg)) filter_ = {} for key, value in six.iteritems(filters): if key in {"secure", "linux", "empty", From 90db2db7b48c4f67ea4a9098c36dfd5c42a24ddb Mon Sep 17 00:00:00 2001 From: Dylan Grafmyre Date: Wed, 11 Feb 2015 23:46:11 -0800 Subject: [PATCH 6/9] Pylint for source module. --- valve/source/__init__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/valve/source/__init__.py b/valve/source/__init__.py index 8b13789..0ae6d43 100644 --- a/valve/source/__init__.py +++ b/valve/source/__init__.py @@ -1 +1,4 @@ +""" + Source Engine specific tools. +""" From 8e64e649eec4f104c9906714f421a3825ad8f1e6 Mon Sep 17 00:00:00 2001 From: Dylan Grafmyre Date: Thu, 12 Feb 2015 00:03:15 -0800 Subject: [PATCH 7/9] Pylint for rcon.py --- valve/source/rcon.py | 143 ++++++++++++++++++++++++++----------------- 1 file changed, 88 insertions(+), 55 deletions(-) diff --git a/valve/source/rcon.py b/valve/source/rcon.py index e24347e..4a95568 100644 --- a/valve/source/rcon.py +++ b/valve/source/rcon.py @@ -18,31 +18,72 @@ WOULDBLOCK = [errno.EAGAIN, errno.EWOULDBLOCK] if os.name == "nt": + # pylint: disable=no-member WOULDBLOCK.append(errno.WSAEWOULDBLOCK) + # pylint: enable=no-member class IncompleteMessageError(Exception): + """ + Module specific exception. + """ pass class AuthenticationError(Exception): + """ + Module specific exception. + """ pass class NoResponseError(Exception): + """ + Module specific exception. + """ pass +class ResponseContextManager(object): + """ + Response context manager. + """ + + def __init__(self, rcon, request, timeout): + self.rcon = rcon + self.request = request + self.timeout = timeout + + def __enter__(self): + time_left = self.timeout + while self.request.response is None: + time_start = time.time() + try: + self.rcon.process() + except IncompleteMessageError: + pass + time_left -= time.time() - time_start + if time_left < 0: + raise NoResponseError + return self.request.response + + def __exit__(self, my_type, value, traceback): + pass + + class Message(object): + """ + Message + """ SERVERDATA_AUTH = 3 SERVERDATA_AUTH_RESPONSE = 2 SERVERDATA_EXECCOMAND = 2 SERVERDATA_RESPONSE_VALUE = 0 - def __init__(self, id, type, body=""): - self.id = id - self.type = type + def __init__(self, identifier, my_type, body=""): + self.identifier = identifier + self.type = my_type self.body = body self.response = None @@ -55,7 +96,7 @@ def __str__(self): } return "{type} ({id}) '{body}'".format( type=types.get(self.type, "INVALID"), - id=self.id, + id=self.identifier, body=" ".join([c.encode("hex") for c in self.body]) ) @@ -74,11 +115,11 @@ def encode(self): by a null-terimnated ASCII-encoded string and a further trailing null terminator. """ - return (struct.pack(b"".format(rcon.host, rcon.port)) - def prompt(prompt=None): - if prompt: - return raw_input("{}: ".format(prompt)) - else: - return raw_input("{}:{}>".format(rcon.host, rcon.port)) +def shell(rcon=None): + """ + Start interactive RCON shell. + """ if rcon is None: - rcon = RCON((prompt("host"), int(prompt("port")))) + rcon = RCON((prompt(rcon, "host"), int(prompt("port")))) if not rcon.is_authenticated: - rcon.authenticate(prompt("password")) + rcon.authenticate(prompt(rcon, "password")) while True: - cmd = rcon.execute(prompt()) + cmd = rcon.execute(prompt(rcon)) with rcon.response_to(cmd) as response: print(response.body) From a1e78e37b8af9b5d4fbdc3a410e58f28ad7a2cf5 Mon Sep 17 00:00:00 2001 From: Dylan Grafmyre Date: Thu, 12 Feb 2015 00:28:32 -0800 Subject: [PATCH 8/9] =?UTF-8?q?Pylint=20for=20messages.py,=20=E0=B2=A0=5F?= =?UTF-8?q?=E0=B2=A0=20:boom:?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- valve/source/messages.py | 109 +++++++++++++++++++++++++-------------- 1 file changed, 70 insertions(+), 39 deletions(-) diff --git a/valve/source/messages.py b/valve/source/messages.py index ae9f8f9..139e62b 100644 --- a/valve/source/messages.py +++ b/valve/source/messages.py @@ -1,6 +1,9 @@ # -*- coding: utf-8 -*- # Copyright (C) 2013 Oliver Ainsworth +# TODO: Document this module. +# pylint: disable=missing-docstring + from __future__ import (absolute_import, unicode_literals, print_function, division) @@ -27,19 +30,24 @@ def __init__(self, message="Incomplete message"): def use_default(func): - def use_default(self, value=None, values={}): + """ + Returns the default callable. + """ + def _use_default(self, value=None, values=None): + if not values: + values = dict() if value is None: return func(self, self.default_value, values) return func(self, value, values) - return use_default + return _use_default def needs_buffer(func): - def needs_buffer(self, buffer, *args, **kwargs): - if len(buffer) == 0: + def _needs_buffer(self, my_buffer, *args, **kwargs): + if len(my_buffer) == 0: raise BufferExhaustedError - return func(self, buffer, *args, **kwargs) - return needs_buffer + return func(self, my_buffer, *args, **kwargs) + return _needs_buffer class MessageField(object): @@ -48,7 +56,7 @@ class MessageField(object): validators = [] def __init__(self, name, optional=False, - default_value=None, validators=[]): + default_value=None, validators=None): """ name -- used when decoding messages to set the key in the returned dictionary @@ -62,6 +70,8 @@ def __init__(self, name, optional=False, validators -- list of callables that return False if the value they're passed is invalid """ + if not validators: + validators = list() if self.fmt is not None: if self.fmt[0] not in "@=<>!": @@ -96,14 +106,16 @@ def validate(self, value): return value @use_default - def encode(self, value, values={}): + def encode(self, value, values=None): + if not values: + values = dict() try: return struct.pack(self.format, self.validate(value)) except struct.error as exc: raise BrokenMessageError(exc) @needs_buffer - def decode(self, buffer, values={}): + def decode(self, my_buffer, _=None): """ Accepts a string of raw bytes which it will attempt to decode into some Python object which is returned. All @@ -122,10 +134,10 @@ def decode(self, buffer, values={}): """ field_size = struct.calcsize(self.format) - if len(buffer) < field_size: + if len(my_buffer) < field_size: raise BufferExhaustedError - field_data = buffer[:field_size] - left_overs = buffer[field_size:] + field_data = my_buffer[:field_size] + left_overs = my_buffer[field_size:] try: return (self.validate( struct.unpack(self.format, field_data)[0]), left_overs) @@ -141,17 +153,21 @@ class StringField(MessageField): fmt = "s" @use_default - def encode(self, value, values={}): + def encode(self, value, values=None): + if not values: + values = dict() return value.encode("utf8") + b"\x00" @needs_buffer - def decode(self, buffer, values={}): - terminator = buffer.find(b"\x00") + def decode(self, my_buffer, values=None): + if not values: + values = dict() + terminator = my_buffer.find(b"\x00") if terminator == -1: raise BufferExhaustedError("No string terminator") field_size = terminator + 1 - field_data = buffer[:field_size-1] - left_overs = buffer[field_size:] + field_data = my_buffer[:field_size-1] + left_overs = my_buffer[field_size:] return field_data.decode("utf8", "ignore"), left_overs @@ -170,18 +186,22 @@ class FloatField(MessageField): class PlatformField(ByteField): @needs_buffer - def decode(self, buffer, values={}): + def decode(self, my_buffer, values=None): + if not values: + values = dict() byte, remnant_buffer = super(PlatformField, - self).decode(buffer, values) + self).decode(my_buffer, values) return util.Platform(byte), remnant_buffer class ServerTypeField(ByteField): @needs_buffer - def decode(self, buffer, values={}): + def decode(self, my_buffer, values=None): + if not values: + values = dict() byte, remnant_buffer = super(ServerTypeField, - self).decode(buffer, values) + self).decode(my_buffer, values) return util.ServerType(byte), remnant_buffer @@ -192,6 +212,9 @@ class MessageArrayField(MessageField): same message.) """ + # TODO: What The Fuc-:boom: + # pylint: disable=unused-argument, dangerous-default-value + def __init__(self, name, element, count=None): """ element -- the Message subclass that will attempt to be decoded @@ -244,7 +267,7 @@ def encode(self, elements, values={}): raise BrokenMessageError("Too few elements") return b"".join(buf) - def decode(self, buffer, values={}): + def decode(self, my_buffer, values={}): entries = [] count = 0 while count < self.count(values): @@ -274,10 +297,10 @@ def decode(self, buffer, values={}): # FF FF FF 00 bytes and stored as message payload. # # This is very much an edge case. :/ - start_buffer = buffer + start_buffer = my_buffer try: - entry = self.element.decode(buffer) - buffer = entry.payload + entry = self.element.decode(my_buffer) + my_buffer = entry.payload entries.append(entry) count += 1 except (BufferExhaustedError, BrokenMessageError) as exc: @@ -285,9 +308,9 @@ def decode(self, buffer, values={}): # buffer is reached. if count < self.count.minimum: raise BrokenMessageError(exc) - buffer = start_buffer + my_buffer = start_buffer break - return entries, buffer + return entries, my_buffer @staticmethod def value_of(name): @@ -295,8 +318,8 @@ def value_of(name): Reference another field's value as the argument 'count'. """ - def field(values={}, f=None): - f.minimum = values[name] + def field(values={}, file_handle=None): + file_handle.minimum = values[name] return values[name] if six.PY3: @@ -343,6 +366,8 @@ def at_least(values={}): at_least.minimum = minimum return at_least + # TODO: What The Fuc-:boom: + # pylint: enable=unused-argument, dangerous-default-value class MessageDictField(MessageArrayField): """ @@ -367,13 +392,15 @@ def __init__(self, name, key_field, value_field, count=None): self.value_field = value_field MessageArrayField.__init__(self, name, element, count) - def decode(self, buffer, values={}): - entries, buffer = MessageArrayField.decode(self, buffer, values) + def decode(self, my_buffer, values=None): + if not values: + values = dict() + entries, my_buffer = MessageArrayField.decode(self, my_buffer, values) entries_dict = {} for entry in entries: entries_dict[entry[ self.key_field.name]] = entry[self.value_field.name] - return entries_dict, buffer + return entries_dict, my_buffer class Message(collections.Mapping): @@ -409,11 +436,13 @@ def encode(self, **field_values): @classmethod def decode(cls, packet): - buffer = packet + my_buffer = packet values = {} for field in cls.fields: - values[field.name], buffer = field.decode(buffer, values) - return cls(buffer, **values) + values[field.name], my_buffer = field.decode(my_buffer, values) + # pylint: disable=star-args + return cls(my_buffer, **values) + # pylint: enable=star-args class Header(Message): @@ -541,11 +570,13 @@ class MSAddressEntryPortField(MessageField): class MSAddressEntryIPField(MessageField): @needs_buffer - def decode(self, buffer, values={}): - if len(buffer) < 4: + def decode(self, my_buffer, values=None): + if not values: + values = dict() + if len(my_buffer) < 4: raise BufferExhaustedError - field_data = buffer[:4] - left_overs = buffer[4:] + field_data = my_buffer[:4] + left_overs = my_buffer[4:] return (".".join(six.text_type(b) for b in struct.unpack(b" Date: Thu, 12 Feb 2015 00:53:57 -0800 Subject: [PATCH 9/9] Pylint for a few files --- valve/steam/api/interface.py | 59 +++++++++++++++------------- valve/steam/client.py | 3 +- valve/steam/id.py | 74 ++++++++++++++++++------------------ 3 files changed, 72 insertions(+), 64 deletions(-) diff --git a/valve/steam/api/interface.py b/valve/steam/api/interface.py index 8ea7a13..0ea21e0 100644 --- a/valve/steam/api/interface.py +++ b/valve/steam/api/interface.py @@ -23,9 +23,9 @@ API_RESPONSE_FORMATS = {"json", "vdf", "xml"} -def api_response_format(format): - if format not in API_RESPONSE_FORMATS: - raise ValueError("Bad response format {!r}".format(format)) +def api_response_format(my_format): + if my_format not in API_RESPONSE_FORMATS: + raise ValueError("Bad response format {!r}".format(my_format)) def decorator(function): @@ -108,6 +108,8 @@ def int32(value): class BaseInterface(object): + name = str() + def __init__(self, api): self._api = api @@ -350,8 +352,10 @@ def make_interfaces(api_list, versions): class API(object): api_root = "https://api.steampowered.com/" + _interfaces = None - def __init__(self, key=None, format="json", versions=None, interfaces=None): + def __init__(self, key=None, my_format="json", versions=None, + interfaces=None): """Initialise an API wrapper The API is usable without an API key but exposes significantly less @@ -361,8 +365,8 @@ def __init__(self, key=None, format="json", versions=None, interfaces=None): the Steam Web API and turn it into a more usable Python object, such as dictionary. The Steam API it self can generate responses in either JSON, XML or VDF. The formatter callables should have an attribute - ``format`` which is a string indicating which textual format they - handle. For convenience the ``format`` parameter also accepts the + ``my_format`` which is a string indicating which textual format they + handle. For convenience the ``my_format`` parameter also accepts the strings ``json``, ``xml`` and ``vdf`` which are mapped to the :func:`json_format`, :func:`etree_format` and :func:`vdf_format` formatters respectively. @@ -381,24 +385,24 @@ def __init__(self, key=None, format="json", versions=None, interfaces=None): behaviour is to use the method with the highest version number. :param str key: a Steam Web API key. - :param format: response formatter. + :param my_format: response formatter. :param versions: the interface method versions to use. :param interfaces: a module containing :class:`BaseInterface` subclasses or ``None`` if they should be loaded for the first time. """ self.key = key - if format == "json": - format = json_format - elif format == "xml": - format = etree_format - elif format == "vdf": - format = vdf_format - self.format = format + if my_format == "json": + my_format = json_format + elif my_format == "xml": + my_format = etree_format + elif my_format == "vdf": + my_format = vdf_format + self.format = my_format self._session = requests.Session() if interfaces is None: self._interfaces_module = make_interfaces( self.request("GET", "ISteamWebAPIUtil", - "GetSupportedAPIList", 1, format=json_format), + "GetSupportedAPIList", 1, my_format=json_format), versions or {}, ) else: @@ -428,37 +432,40 @@ def _bind_interfaces(self): # Not a class continue + # pylint: disable=unused-argument def request(self, http_method, interface, - method, version, params=None, format=None): + method, version, params=None, my_format=None): """Issue a HTTP request to the Steam Web API This is called indirectly by interface methods and should rarely be called directly. The response to the request is passed through the response formatter which is then returned. - :param str interface: the name of the interface. - :param str method: the name of the method on the interface. - :param int version: the version of the method. + :param str interface: the name of the interface. (UNUSED) + :param str method: the name of the method on the interface. (UNUSED) + :param int version: the version of the method. (UNUSED) :param params: a mapping of GET or POST data to be sent with the request. - :param format: a response formatter callable to overide :attr:`format`. + :param my_format: a response formatter callable to + overide :attr:`my_format`. """ if params is None: params = {} - if format is None: - format = self.format + if my_format is None: + my_format = self.format path = "{interface}/{method}/v{version}/".format(**locals()) - if format.format not in API_RESPONSE_FORMATS: + if my_format.format not in API_RESPONSE_FORMATS: raise ValueError("Response formatter specifies its format as " "{!r}, but only 'json', 'xml' and 'vdf' " - "are permitted values".format(format.format)) - params["format"] = format.format + "are permitted values".format(my_format.format)) + params["format"] = my_format.format if "key" in params: del params["key"] if self.key: params["key"] = self.key - return format(self._session.request(http_method, + return my_format(self._session.request(http_method, self.api_root + path, params).text) + # pylint: enable=unused-argument @contextlib.contextmanager def session(self): diff --git a/valve/steam/client.py b/valve/steam/client.py index 201a728..44f1452 100644 --- a/valve/steam/client.py +++ b/valve/steam/client.py @@ -8,6 +8,7 @@ """ import itertools +# TODO: Cross platform support. import _winreg as winreg import os @@ -60,7 +61,7 @@ def __init__(self, **kwargs): def _get_registry_key(self, *args): args = list(itertools.chain(*[str(arg).split("\\") for arg in args])) - sub_key = "Software\\Valve\Steam\\" + "\\".join(args[:-1]) + sub_key = "Software\\Valve\\Steam\\" + "\\".join(args[:-1]) if self.registry_access_flag is not None: access_flag = self.registry_access_flag | winreg.KEY_QUERY_VALUE else: diff --git a/valve/steam/id.py b/valve/steam/id.py index ee431f7..db54cdc 100644 --- a/valve/steam/id.py +++ b/valve/steam/id.py @@ -20,14 +20,14 @@ UNIVERSE_DEV = 4 #: UNIVERSE_RC = 5 #: -_universes = [ +_UNIVERSES = [ UNIVERSE_INDIVIDUAL, UNIVERSE_PUBLIC, UNIVERSE_BETA, UNIVERSE_INTERNAL, UNIVERSE_DEV, UNIVERSE_RC, - ] +] TYPE_INVALID = 0 #: TYPE_INDIVIDUAL = 1 #: @@ -41,7 +41,7 @@ TYPE_P2P_SUPER_SEEDER = 9 #: TYPE_ANON_USER = 10 #: -_types = [ +_TYPES = [ TYPE_INVALID, TYPE_INDIVIDUAL, TYPE_MULTISEAT, @@ -53,24 +53,24 @@ TYPE_CHAT, TYPE_P2P_SUPER_SEEDER, TYPE_ANON_USER, - ] +] -type_letter_map = { +TYPE_LETTER_MAP = { TYPE_INDIVIDUAL: "U", TYPE_CLAN: "g", TYPE_CHAT: "T", - } -letter_type_map = {v: k for k, v in type_letter_map.items()} +} +LETTER_TYPE_MAP = {v: k for k, v in TYPE_LETTER_MAP.items()} -type_url_path_map = { +TYPE_URL_PATH_MAP = { TYPE_INDIVIDUAL: ["profiles", "id"], TYPE_CLAN: ["groups", "gid"], - } +} # These shall be left as homage to Great Line-feed Drought of 2013 -textual_id_regex = re.compile(r"^STEAM_(?P\d+):(?P\d+):(?P\d+)$") -community32_regex = re.compile(r".*/(?P{paths})/\[(?P[{type_chars}]):1:(?P\d+)\]$".format(paths="|".join("|".join(paths) for paths in type_url_path_map.values()), type_chars="".join(c for c in type_letter_map.values()))) -community64_regex = re.compile(r".*/(?P{paths})/(?P\d+)$".format(paths="|".join("|".join(paths) for paths in type_url_path_map.values()))) +TEXTUAL_ID_REGEX = re.compile(r"^STEAM_(?P\d+):(?P\d+):(?P\d+)$") +COMMUNITY32_REGEX = re.compile(r".*/(?P{paths})/\[(?P[{type_chars}]):1:(?P\d+)\]$".format(paths="|".join("|".join(paths) for paths in TYPE_URL_PATH_MAP.values()), type_chars="".join(c for c in TYPE_LETTER_MAP.values()))) +COMMUNITY64_REGEX = re.compile(r".*/(?P{paths})/(?P\d+)$".format(paths="|".join("|".join(paths) for paths in TYPE_URL_PATH_MAP.values()))) class SteamIDError(ValueError): @@ -138,7 +138,7 @@ class SteamID(object): base_community_url = "http://steamcommunity.com/" @classmethod - def from_community_url(cls, id, universe=UNIVERSE_INDIVIDUAL): + def from_community_url(cls, identifier, universe=UNIVERSE_INDIVIDUAL): """Parse a Steam community URL into a :class:`.SteamID` instance This takes a Steam community URL for a profile or group and converts @@ -153,39 +153,39 @@ def from_community_url(cls, id, universe=UNIVERSE_INDIVIDUAL): Raises :class:`.SteamIDError` if the URL cannot be parsed. """ - url = urlparse.urlparse(id) - match = community32_regex.match(url.path) + url = urlparse.urlparse(identifier) + match = COMMUNITY32_REGEX.match(url.path) if match: if (match.group("path") not in - type_url_path_map[letter_type_map[match.group("type")]]): + TYPE_URL_PATH_MAP[LETTER_TYPE_MAP[match.group("type")]]): warnings.warn("Community URL ({}) path doesn't " "match type character".format(url.path)) w = int(match.group("W")) y = w & 1 z = (w - y) / 2 - return cls(z, y, letter_type_map[match.group("type")], universe) - match = community64_regex.match(url.path) + return cls(z, y, LETTER_TYPE_MAP[match.group("type")], universe) + match = COMMUNITY64_REGEX.match(url.path) if match: w = int(match.group("W")) y = w & 1 - if match.group("path") in type_url_path_map[TYPE_INDIVIDUAL]: + if match.group("path") in TYPE_URL_PATH_MAP[TYPE_INDIVIDUAL]: z = (w - y - 0x0110000100000000) / 2 - type = TYPE_INDIVIDUAL - elif match.group("path") in type_url_path_map[TYPE_CLAN]: + my_type = TYPE_INDIVIDUAL + elif match.group("path") in TYPE_URL_PATH_MAP[TYPE_CLAN]: z = (w - y - 0x0170000000000000) / 2 - type = TYPE_CLAN - return cls(z, y, type, universe) + my_type = TYPE_CLAN + return cls(z, y, my_type, universe) raise SteamIDError("Invalid Steam community URL ({})".format(url)) @classmethod - def from_text(cls, id, type=TYPE_INDIVIDUAL): + def from_text(cls, identifier, my_type=TYPE_INDIVIDUAL): """Parse a SteamID in the STEAM_X:Y:Z form Takes a teaxtual SteamID in the form STEAM_X:Y:Z and returns a corresponding :class:`.SteamID` instance. The X represents the account's 'universe,' Z is the account number and Y is either 1 or 0. - As the account type cannot be directly inferred from the SteamID + As the account my_type cannot be directly inferred from the SteamID it must be explicitly specified, defaulting to :data:`TYPE_INDIVIDUAL`. The two special IDs ``STEAM_ID_PENDING`` and ``UNKNOWN`` are also @@ -194,26 +194,26 @@ def from_text(cls, id, type=TYPE_INDIVIDUAL): and with all other components of the ID set to zero. """ - if id == "STEAM_ID_PENDING": + if identifier == "STEAM_ID_PENDING": return cls(0, 0, TYPE_PENDING, 0) - if id == "UNKNOWN": + if identifier == "UNKNOWN": return cls(0, 0, TYPE_INVALID, 0) - match = textual_id_regex.match(id) + match = TEXTUAL_ID_REGEX.match(identifier) if not match: raise SteamIDError("ID '{}' doesn't match format {}".format( - id, textual_id_regex.pattern)) + identifier, TEXTUAL_ID_REGEX.pattern)) return cls( int(match.group("Z")), int(match.group("Y")), - type, + my_type, int(match.group("X")) ) - def __init__(self, account_number, instance, type, universe): - if universe not in _universes: + def __init__(self, account_number, instance, my_type, universe): + if universe not in _UNIVERSES: raise SteamIDError("Invalid universe {}".format(universe)) - if type not in _types: - raise SteamIDError("Invalid type {}".format(type)) + if my_type not in _TYPES: + raise SteamIDError("Invalid type {}".format(my_type)) if account_number < 0 or account_number > (2**32) - 1: raise SteamIDError( "Account number ({}) out of range".format(account_number)) @@ -222,7 +222,7 @@ def __init__(self, account_number, instance, type, universe): "Expected instance to be 1 or 0, got {}".format(instance)) self.account_number = account_number # Z self.instance = instance # Y - self.type = type + self.type = my_type self.universe = universe # X @property @@ -302,7 +302,7 @@ def as_32(self): try: return "[{}:1:{}]".format( - type_letter_map[self.type], + TYPE_LETTER_MAP[self.type], (self.account_number * 2) + self.instance ) except KeyError: @@ -332,7 +332,7 @@ def community_url(self, id64=True): try: return urlparse.urljoin( self.__class__.base_community_url, - "/".join((type_url_path_map[self.type][0], path_func())) + "/".join((TYPE_URL_PATH_MAP[self.type][0], path_func())) ) except KeyError: raise SteamIDError(