new custom import works better with recursive imports, thanks Paolo Pastori

This commit is contained in:
mdipierro committed 2019-03-24 11:28:28 -07:00
1 parent b4e22bf465
commit 8c090954fd
1 file changed
+27 -57
+27 -57
View File
@@ -8,7 +8,7 @@
Support for smart import syntax for web2py applications Support for smart import syntax for web2py applications
------------------------------------------------------- -------------------------------------------------------
""" """
from gluon._compat import builtin, unicodeT, PY2, to_native, reload from gluon._compat import builtin, unicodeT, to_native, reload
import os import os
import sys import sys
import threading import threading
@@ -19,7 +19,6 @@ INVALID_MODULES = set(('', 'gluon', 'applications', 'custom_import'))
# backward compatibility API # backward compatibility API
def custom_import_install(): def custom_import_install():
if builtin.__import__ == NATIVE_IMPORTER: if builtin.__import__ == NATIVE_IMPORTER:
INVALID_MODULES.update(sys.modules.keys()) INVALID_MODULES.update(sys.modules.keys())
@@ -35,78 +34,56 @@ def is_tracking_changes():
return current.request._custom_import_track_changes return current.request._custom_import_track_changes
class CustomImportException(ImportError): # see https://docs.python.org/3/library/functions.html#__import__
pass # Changed in Python 3.3: Negative values for level are no longer supported,
# which also changes the default value to 0 (was -1)
_DEFAULT_LEVEL = 0 if sys.version_info[:2] >= (3, 3) else -1
def custom_importer(name, globals={}, locals=None, fromlist=(), level=_DEFAULT_LEVEL):
def custom_importer(name, globals=None, locals=None, fromlist=None, level=-1):
""" """
web2py's custom importer. It behaves like the standard Python importer but web2py's custom importer. It behaves like the standard Python importer but
it tries to transform import statements as something like it tries to transform import statements as something like
"import applications.app_name.modules.x". "import applications.app_name.modules.x".
If the import fails, it falls back on naive_importer If the import fails, it falls back on builtin importer.
""" """
# support for non-ascii name
if isinstance(name, unicodeT): if isinstance(name, unicodeT):
name = to_native(name) name = to_native(name)
globals = globals or {} if hasattr(current, 'request') \
locals = locals or {} and level <= 0 \
fromlist = fromlist or [] and name.partition('.')[0] not in INVALID_MODULES:
# absolute import from application code
try: try:
return NATIVE_IMPORTER(name, globals, locals, fromlist, level)
except (ImportError, KeyError):
pass
if current.request._custom_import_track_changes: if current.request._custom_import_track_changes:
base_importer = TRACK_IMPORTER base_importer = TRACK_IMPORTER
else: else:
base_importer = NATIVE_IMPORTER base_importer = NATIVE_IMPORTER
except: # there is no current.request (should never happen)
base_importer = NATIVE_IMPORTER
if not(PY2) and level < 0:
level = 0
# if not relative and not from applications:
if hasattr(current, 'request') \
and level <= 0 \
and not name.partition('.')[0] in INVALID_MODULES \
and isinstance(globals, dict):
import_tb = None
try:
try:
oname = name if not name.startswith('.') else '.'+name
return NATIVE_IMPORTER(oname, globals, locals, fromlist, level)
except (ImportError, KeyError):
items = current.request.folder.split(os.path.sep) items = current.request.folder.split(os.path.sep)
if not items[-1]: # FIXME: why does request.folder endswith(os.path.sep) ?
items = items[:-1] if not items[-1]: items.pop()
modules_prefix = '.'.join(items[-2:]) + '.modules' modules_prefix = '.'.join(items[-2:]) + '.modules'
if not fromlist: if not fromlist:
# import like "import x" or "import x.y" # "import x" or "import x.y"
result = None result = None
for itemname in name.split("."): for itemname in name.split("."):
new_mod = base_importer( new_mod = base_importer(
modules_prefix, globals, locals, [itemname], level) modules_prefix, globals, locals, (itemname,), level)
try:
result = result or sys.modules[modules_prefix+'.'+itemname]
except KeyError as e:
raise ImportError('Cannot import module %s' % str(e))
modules_prefix += "." + itemname modules_prefix += "." + itemname
if result is None:
try:
result = sys.modules[modules_prefix]
except KeyError as e:
raise ImportError("No module named %s" % e)
return result return result
else: else:
# import like "from x import a, b, ..." # "from x import a, b, ..."
pname = modules_prefix + "." + name pname = "%s.%s" % (modules_prefix, name)
return base_importer(pname, globals, locals, fromlist, level) return base_importer(pname, globals, locals, fromlist, level)
except ImportError as e1:
import_tb = sys.exc_info()[2]
try:
return NATIVE_IMPORTER(name, globals, locals, fromlist, level)
except (ImportError, KeyError) as e3:
raise ImportError(e1, import_tb) # there an import error in the module
except Exception as e2:
raise # there is an error in the module
finally:
if import_tb:
import_tb = None
return NATIVE_IMPORTER(name, globals, locals, fromlist, level) return NATIVE_IMPORTER(name, globals, locals, fromlist, level)
@@ -123,14 +100,10 @@ class TrackImporter(object):
def __init__(self): def __init__(self):
self._import_dates = {} # Import dates of the files of the modules self._import_dates = {} # Import dates of the files of the modules
def __call__(self, name, globals=None, locals=None, fromlist=None, level=-1): def __call__(self, name, globals={}, locals=None, fromlist=(), level=_DEFAULT_LEVEL):
""" """
The import method itself. The import method itself.
""" """
globals = globals or {}
locals = locals or {}
fromlist = fromlist or []
try:
# Check the date and reload if needed: # Check the date and reload if needed:
self._update_dates(name, globals, locals, fromlist, level) self._update_dates(name, globals, locals, fromlist, level)
# Try to load the module and update the dates if it works: # Try to load the module and update the dates if it works:
@@ -138,15 +111,12 @@ class TrackImporter(object):
# Module maybe loaded for the 1st time so we need to set the date # Module maybe loaded for the 1st time so we need to set the date
self._update_dates(name, globals, locals, fromlist, level) self._update_dates(name, globals, locals, fromlist, level)
return result return result
except Exception as e:
raise # Don't hide something that went wrong
def _update_dates(self, name, globals, locals, fromlist, level): def _update_dates(self, name, globals, locals, fromlist, level):
""" """
Update all the dates associated to the statement import. A single Update all the dates associated to the statement import. A single
import statement may import many modules. import statement may import many modules.
""" """
self._reload_check(name, globals, locals, level) self._reload_check(name, globals, locals, level)
for fromlist_name in fromlist or []: for fromlist_name in fromlist or []:
pname = "%s.%s" % (name, fromlist_name) pname = "%s.%s" % (name, fromlist_name)