diff --git a/evennia/utils/utils.py b/evennia/utils/utils.py index fbd9f88fe7..832b8a01ed 100644 --- a/evennia/utils/utils.py +++ b/evennia/utils/utils.py @@ -6,8 +6,6 @@ They provide some useful string and conversion methods that might be of use when designing your own game. """ - -from builtins import object, range from future.utils import viewkeys, raise_ import os @@ -20,6 +18,7 @@ import textwrap import random from os.path import join as osjoin from importlib import import_module +from importlib.util import find_spec, module_from_spec from inspect import ismodule, trace, getmembers, getmodule from collections import defaultdict, OrderedDict from twisted.internet import threads, reactor, task @@ -32,10 +31,7 @@ _MULTIMATCH_TEMPLATE = settings.SEARCH_MULTIMATCH_TEMPLATE _EVENNIA_DIR = settings.EVENNIA_DIR _GAME_DIR = settings.GAME_DIR -try: - import pickle as pickle -except ImportError: - import pickle +import pickle ENCODINGS = settings.ENCODINGS _GA = object.__getattribute__ @@ -45,15 +41,15 @@ _DA = object.__delattr__ _DEFAULT_WIDTH = settings.CLIENT_DEFAULT_WIDTH -def is_iter(iterable): +def is_iter(obj): """ Checks if an object behaves iterably. Args: - iterable (any): Entity to check for iterability. + obj (any): Entity to check for iterability. Returns: - is_iterable (bool): If `iterable` is iterable or not. + is_iterable (bool): If `obj` is iterable or not. Notes: Strings are *not* accepted as iterable (although they are @@ -61,7 +57,13 @@ def is_iter(iterable): what we want to do with a string. """ - return hasattr(iterable, '__iter__') + if isinstance(obj, (str, bytes, )): + return False + + try: + return iter(obj) and True + except TypeError: + return False def make_iter(obj): @@ -76,7 +78,7 @@ def make_iter(obj): passed-through or made iterable. """ - return not hasattr(obj, '__iter__') and [obj] or obj + return not is_iter(obj) and [obj] or obj def wrap(text, width=_DEFAULT_WIDTH, indent=0): @@ -599,7 +601,7 @@ def dbref(inp, reqhash=True): inp.startswith("#") and inp.lstrip('#').isdigit()) else None) - return num if num > 0 else None + return num if isinstance(num, int) and num > 0 else None elif isinstance(inp, str): inp = inp.lstrip('#') return int(inp) if inp.isdigit() and int(inp) > 0 else None @@ -702,6 +704,10 @@ def latinify(unicode_string, default='?', pure_ascii=False): def to_unicode(obj, encoding='utf-8', force_string=False): """ + This function is deprecated in the Python 3 version of Evennia and is + likely to be phased out in future releases. + + --- This decodes a suitable object to the unicode format. Args: @@ -723,35 +729,23 @@ def to_unicode(obj, encoding='utf-8', force_string=False): """ - if force_string and not isinstance(obj, str): + if isinstance(obj, (str, bytes, )): + return obj + + if force_string: # some sort of other object. Try to # convert it to a string representation. - if hasattr(obj, '__str__'): - obj = obj.__str__() - elif hasattr(obj, '__unicode__'): - obj = obj.__unicode__() - else: - # last resort - obj = str(obj) + obj = str(obj) - if isinstance(obj, str) and not isinstance(obj, str): - try: - obj = str(obj, encoding) - return obj - except UnicodeDecodeError: - for alt_encoding in ENCODINGS: - try: - obj = str(obj, alt_encoding) - return obj - except UnicodeDecodeError: - # if we still have an error, give up - pass - raise Exception("Error: '%s' contains invalid character(s) not in %s." % (obj, encoding)) return obj def to_str(obj, encoding='utf-8', force_string=False): """ + This function is deprecated in the Python 3 version of Evennia and is + likely to be phased out in future releases. + + --- This encodes a unicode string back to byte-representation, for printing, writing to disk etc. @@ -768,32 +762,14 @@ def to_str(obj, encoding='utf-8', force_string=False): conversion of objects to strings. """ - if force_string and not isinstance(obj, str): + if isinstance(obj, (str, bytes, )): + return obj + + if force_string: # some sort of other object. Try to # convert it to a string representation. - try: - obj = str(obj) - except Exception: - obj = str(obj) + obj = str(obj) - if isinstance(obj, str) and isinstance(obj, str): - try: - obj = obj.encode(encoding) - return obj - except UnicodeEncodeError: - for alt_encoding in ENCODINGS: - try: - obj = obj.encode(alt_encoding) - return obj - except UnicodeEncodeError: - # if we still have an error, give up - pass - - # if we get to this point we have not found any way to convert this string. Try to parse it manually, - try: - return latinify(obj, '?') - except Exception as err: - raise Exception("%s, Error: Unicode could not encode unicode string '%s'(%s) to a bytestring. " % (err, obj, encoding)) return obj @@ -1355,7 +1331,7 @@ def class_from_module(path, defaultpaths=None): Args: path (str): Full Python dot-path to module. - defaultpaths (iterable, optional): If a direc import from `path` fails, + defaultpaths (iterable, optional): If a direct import from `path` fails, try subsequent imports by prepending those paths to `path`. Returns: @@ -1376,17 +1352,18 @@ def class_from_module(path, defaultpaths=None): testpath, clsname = testpath.rsplit(".", 1) else: raise ImportError("the path '%s' is not on the form modulepath.Classname." % path) + try: - mod = import_module(testpath, package="evennia") - except ImportError: - if len(trace()) > 2: - # this means the error happened within the called module and - # we must not hide it. - exc = sys.exc_info() - raise_(exc[1], None, exc[2]) - else: - # otherwise, try the next suggested path + if not find_spec(testpath, package='evennia'): continue + except ModuleNotFoundError: + continue + + try: + mod = import_module(testpath, package='evennia') + except ModuleNotFoundError: + break + try: cls = getattr(mod, clsname) break