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

This commit is contained in:
mdipierro
2019-03-24 11:28:28 -07:00
parent b4e22bf465
commit 8c090954fd
+46 -76
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) items = current.request.folder.split(os.path.sep)
base_importer = NATIVE_IMPORTER # FIXME: why does request.folder endswith(os.path.sep) ?
if not items[-1]: items.pop()
if not(PY2) and level < 0: modules_prefix = '.'.join(items[-2:]) + '.modules'
level = 0 if not fromlist:
# "import x" or "import x.y"
# if not relative and not from applications: result = None
if hasattr(current, 'request') \ for itemname in name.split("."):
and level <= 0 \ new_mod = base_importer(
and not name.partition('.')[0] in INVALID_MODULES \ modules_prefix, globals, locals, (itemname,), level)
and isinstance(globals, dict): modules_prefix += "." + itemname
import_tb = None if result is None:
try: try:
try: result = sys.modules[modules_prefix]
oname = name if not name.startswith('.') else '.'+name except KeyError as e:
return NATIVE_IMPORTER(oname, globals, locals, fromlist, level) raise ImportError("No module named %s" % e)
except (ImportError, KeyError): return result
items = current.request.folder.split(os.path.sep) else:
if not items[-1]: # "from x import a, b, ..."
items = items[:-1] pname = "%s.%s" % (modules_prefix, name)
modules_prefix = '.'.join(items[-2:]) + '.modules' return base_importer(pname, globals, locals, fromlist, level)
if not fromlist:
# import like "import x" or "import x.y"
result = None
for itemname in name.split("."):
new_mod = base_importer(
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
return result
else:
# import like "from x import a, b, ..."
pname = modules_prefix + "." + name
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,30 +100,23 @@ 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 {} # Check the date and reload if needed:
locals = locals or {} self._update_dates(name, globals, locals, fromlist, level)
fromlist = fromlist or [] # Try to load the module and update the dates if it works:
try: result = NATIVE_IMPORTER(name, globals, locals, fromlist, level)
# Check the date and reload if needed: # 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)
# Try to load the module and update the dates if it works: return result
result = NATIVE_IMPORTER(name, globals, locals, fromlist, level)
# Module maybe loaded for the 1st time so we need to set the date
self._update_dates(name, globals, locals, fromlist, level)
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)
@@ -169,7 +139,7 @@ class TrackImporter(object):
except: except:
self._import_dates.pop(file, None) # Clean up self._import_dates.pop(file, None) # Clean up
# Handle module changing in package and # Handle module changing in package and
#package changing in module: # package changing in module:
if file.endswith(".py"): if file.endswith(".py"):
# Get path without file ext: # Get path without file ext:
file = os.path.splitext(file)[0] file = os.path.splitext(file)[0]