new custom import works better with recursive imports, thanks Paolo Pastori
This commit is contained in:
+46
-76
@@ -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]
|
||||||
|
|||||||
Reference in New Issue
Block a user