Refactor mod_import to use importlib
Switch from the deprecated imp to importlib. Also add tests and clean up logic flow. This should be quite a bit faster than the old implementation as well.
This commit is contained in:
parent
e395ea9371
commit
da48fa2e52
2 changed files with 60 additions and 34 deletions
|
|
@ -5,6 +5,8 @@ TODO: Not nearly all utilities are covered yet.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import os.path
|
||||||
|
|
||||||
import mock
|
import mock
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
@ -203,3 +205,27 @@ class TestDateTimeFormat(TestCase):
|
||||||
self.assertEqual(utils.datetime_format(dtobj), "19:54")
|
self.assertEqual(utils.datetime_format(dtobj), "19:54")
|
||||||
dtobj = datetime(2019, 8, 28, 21, 32)
|
dtobj = datetime(2019, 8, 28, 21, 32)
|
||||||
self.assertEqual(utils.datetime_format(dtobj), "21:32:00")
|
self.assertEqual(utils.datetime_format(dtobj), "21:32:00")
|
||||||
|
|
||||||
|
|
||||||
|
class TestImportFunctions(TestCase):
|
||||||
|
def _t_dir_file(self, filename):
|
||||||
|
testdir = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
return os.path.join(testdir, filename)
|
||||||
|
|
||||||
|
def test_mod_import(self):
|
||||||
|
loaded_mod = utils.mod_import('evennia.utils.ansi')
|
||||||
|
self.assertIsNotNone(loaded_mod)
|
||||||
|
|
||||||
|
def test_mod_import_invalid(self):
|
||||||
|
loaded_mod = utils.mod_import('evennia.utils.invalid_module')
|
||||||
|
self.assertIsNone(loaded_mod)
|
||||||
|
|
||||||
|
def test_mod_import_from_path(self):
|
||||||
|
test_path = self._t_dir_file('test_eveditor.py')
|
||||||
|
loaded_mod = utils.mod_import_from_path(test_path)
|
||||||
|
self.assertIsNotNone(loaded_mod)
|
||||||
|
|
||||||
|
def test_mod_import_from_path_invalid(self):
|
||||||
|
test_path = self._t_dir_file('invalid_filename.py')
|
||||||
|
loaded_mod = utils.mod_import_from_path(test_path)
|
||||||
|
self.assertIsNone(loaded_mod)
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@ be of use when designing your own game.
|
||||||
import os
|
import os
|
||||||
import gc
|
import gc
|
||||||
import sys
|
import sys
|
||||||
import imp
|
|
||||||
import types
|
import types
|
||||||
import math
|
import math
|
||||||
import re
|
import re
|
||||||
|
|
@ -17,6 +16,7 @@ import textwrap
|
||||||
import random
|
import random
|
||||||
import inspect
|
import inspect
|
||||||
import traceback
|
import traceback
|
||||||
|
import importlib.machinery
|
||||||
from twisted.internet.task import deferLater
|
from twisted.internet.task import deferLater
|
||||||
from twisted.internet.defer import returnValue # noqa - used as import target
|
from twisted.internet.defer import returnValue # noqa - used as import target
|
||||||
from os.path import join as osjoin
|
from os.path import join as osjoin
|
||||||
|
|
@ -1166,6 +1166,30 @@ def has_parent(basepath, obj):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def mod_import_from_path(path):
|
||||||
|
"""
|
||||||
|
Load a Python module at the specified path.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
path (str): An absolute path to a Python module to load.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
(module or None): An imported module if the path was a valid
|
||||||
|
Python module. Returns `None` if the import failed.
|
||||||
|
|
||||||
|
"""
|
||||||
|
if not os.path.isabs(path):
|
||||||
|
path = os.path.abspath(path)
|
||||||
|
dirpath, filename = path.rsplit(os.path.sep, 1)
|
||||||
|
modname = filename.rstrip('.py')
|
||||||
|
|
||||||
|
try:
|
||||||
|
return importlib.machinery.SourceFileLoader(modname, path).load_module()
|
||||||
|
except OSError:
|
||||||
|
logger.log_trace(f"Could not find module '{modname}' ({modname}.py) at path '{dirpath}'")
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def mod_import(module):
|
def mod_import(module):
|
||||||
"""
|
"""
|
||||||
A generic Python module loader.
|
A generic Python module loader.
|
||||||
|
|
@ -1173,52 +1197,28 @@ def mod_import(module):
|
||||||
Args:
|
Args:
|
||||||
module (str, module): This can be either a Python path
|
module (str, module): This can be either a Python path
|
||||||
(dot-notation like `evennia.objects.models`), an absolute path
|
(dot-notation like `evennia.objects.models`), an absolute path
|
||||||
(e.g. `/home/eve/evennia/evennia/objects.models.py`) or an
|
(e.g. `/home/eve/evennia/evennia/objects/models.py`) or an
|
||||||
already imported module object (e.g. `models`)
|
already imported module object (e.g. `models`)
|
||||||
Returns:
|
Returns:
|
||||||
module (module or None): An imported module. If the input argument was
|
(module or None): An imported module. If the input argument was
|
||||||
already a module, this is returned as-is, otherwise the path is
|
already a module, this is returned as-is, otherwise the path is
|
||||||
parsed and imported. Returns `None` and logs error if import failed.
|
parsed and imported. Returns `None` and logs error if import failed.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if not module:
|
if not module:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
if isinstance(module, types.ModuleType):
|
if isinstance(module, types.ModuleType):
|
||||||
# if this is already a module, we are done
|
# if this is already a module, we are done
|
||||||
mod = module
|
return module
|
||||||
else:
|
|
||||||
# first try to import as a python path
|
|
||||||
try:
|
|
||||||
mod = __import__(module, fromlist=["None"])
|
|
||||||
except ImportError as ex:
|
|
||||||
# check just where the ImportError happened (it could have been
|
|
||||||
# an erroneous import inside the module as well). This is the
|
|
||||||
# trivial way to do it ...
|
|
||||||
if not str(ex).startswith("No module named "):
|
|
||||||
raise
|
|
||||||
|
|
||||||
# error in this module. Try absolute path import instead
|
if module.endswith('.py') and os.path.exists(module):
|
||||||
|
return mod_import_from_path(module)
|
||||||
|
|
||||||
if not os.path.isabs(module):
|
try:
|
||||||
module = os.path.abspath(module)
|
return import_module(module)
|
||||||
path, filename = module.rsplit(os.path.sep, 1)
|
except ImportError:
|
||||||
modname = re.sub(r"\.py$", "", filename)
|
return None
|
||||||
|
|
||||||
try:
|
|
||||||
result = imp.find_module(modname, [path])
|
|
||||||
except ImportError:
|
|
||||||
logger.log_trace("Could not find module '%s' (%s.py) at path '%s'" % (modname, modname, path))
|
|
||||||
return None
|
|
||||||
try:
|
|
||||||
mod = imp.load_module(modname, *result)
|
|
||||||
except ImportError:
|
|
||||||
logger.log_trace("Could not find or import module %s at path '%s'" % (modname, path))
|
|
||||||
mod = None
|
|
||||||
# we have to close the file handle manually
|
|
||||||
result[0].close()
|
|
||||||
return mod
|
|
||||||
|
|
||||||
|
|
||||||
def all_from_module(module):
|
def all_from_module(module):
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue