web2py.gluon.custom_import

Package web2py :: Package gluon :: Module custom_import |
[hide private]
[frames] | no frames]

# Source Code for Module web2py.gluon.custom_import

      1  #!/usr/bin/env python
      2  # -*- coding: utf-8 -*-
      3
      4  import __builtin__
      5  import os
      6  import re
      7  import sys
      8  import threading
      9
     10  # Install the new import function:
     11 -def custom_import_install(web2py_path):
     12      global _web2py_importer
     13      global _web2py_path
     14      if isinstance(__builtin__.__import__, _Web2pyImporter):
     15          return #aready installed
     16      _web2py_path = web2py_path
     17      _web2py_importer = _Web2pyImporter(web2py_path)
     18      __builtin__.__import__ = _web2py_importer
     19
     20 -def is_tracking_changes():
     21      """
     22      @return: True: neo_importer is tracking changes made to Python source
     23      files. False: neo_import does not reload Python modules.
     24      """
     25
     26      global _is_tracking_changes
     27      return _is_tracking_changes
     28
     29 -def track_changes(track=True):
     30      """
     31      Tell neo_importer to start/stop tracking changes made to Python modules.
     32      @param track: True: Start tracking changes. False: Stop tracking changes.
     33      """
     34
     35      global _is_tracking_changes
     36      global _web2py_importer
     37      global _web2py_date_tracker_importer
     38      assert track is True or track is False, "Boolean expected."
     39      if track == _is_tracking_changes:
     40          return
     41      if track:
     42          if not _web2py_date_tracker_importer:
     43              _web2py_date_tracker_importer = \
     44                  _Web2pyDateTrackerImporter(_web2py_path)
     45          __builtin__.__import__ = _web2py_date_tracker_importer
     46      else:
     47          __builtin__.__import__ = _web2py_importer
     48      _is_tracking_changes = track
     49
     50  _STANDARD_PYTHON_IMPORTER = __builtin__.__import__ # Keep standard importer
     51  _web2py_importer = None # The standard web2py importer
     52  _web2py_date_tracker_importer = None # The web2py importer with date tracking
     53  _web2py_path = None # Absolute path of the web2py directory
     54
     55  _is_tracking_changes = False # The tracking mode
     56
     57 -class _BaseImporter(object):
     58      """
     59      The base importer. Dispatch the import the call to the standard Python
     60      importer.
     61      """
     62
     63 -    def begin(self):
     64          """
     65          Many imports can be made for a single import statement. This method
     66          help the management of this aspect.
     67          """
     68
     69 -    def __call__(self, name, globals=None, locals=None,
     70                   fromlist=None, level=-1):
     71          """
     72          The import method itself.
     73          """
     74          return _STANDARD_PYTHON_IMPORTER(name,
     75                                           globals,
     76                                           locals,
     77                                           fromlist,
     78                                           level)
     79
     80 -    def end(self):
     81          """
     82          Needed for clean up.
     83          """
     84
     85
     86 -class _DateTrackerImporter(_BaseImporter):
     87      """
     88      An importer tracking the date of the module files and reloading them when
     89      they have changed.
     90      """
     91
     92      _PACKAGE_PATH_SUFFIX = os.path.sep+"__init__.py"
     93
     94 -    def __init__(self):
     95          super(_DateTrackerImporter, self).__init__()
     96          self._import_dates = {} # Import dates of the files of the modules
     97          # Avoid reloading cause by file modifications of reload:
     98          self._tl = threading.local()
     99          self._tl._modules_loaded = None
    100
    101 -    def begin(self):
    102          self._tl._modules_loaded = set()
    103
    104 -    def __call__(self, name, globals=None, locals=None,
    105                   fromlist=None, level=-1):
    106          """
    107          The import method itself.
    108          """
    109
    110          globals = globals or {}
    111          locals = locals or {}
    112          fromlist = fromlist or []
    113
    114          call_begin_end = self._tl._modules_loaded is None
    115          if call_begin_end:
    116              self.begin()
    117          try:
    118              self._tl.globals = globals
    119              self._tl.locals = locals
    120              self._tl.level = level
    121
    122              # Check the date and reload if needed:
    123              self._update_dates(name, fromlist)
    124
    125              # Try to load the module and update the dates if it works:
    126              result = super(_DateTrackerImporter, self) \
    127                .__call__(name, globals, locals, fromlist, level)
    128              # Module maybe loaded for the 1st time so we need to set the date
    129              self._update_dates(name, fromlist)
    130              return result
    131          except Exception, e:
    132              raise e  # Don't hide something that went wrong
    133          finally:
    134              if call_begin_end:
    135                  self.end()
    136
    137 -    def _update_dates(self, name, fromlist):
    138          """
    139          Update all the dates associated to the statement import. A single
    140          import statement may import many modules.
    141          """
    142
    143          self._reload_check(name)
    144          if fromlist:
    145              for fromlist_name in fromlist:
    146                  self._reload_check("%s.%s" % (name, fromlist_name))
    147
    148 -    def _reload_check(self, name):
    149          """
    150          Update the date associated to the module and reload the module if
    151          the file has changed.
    152          """
    153
    154          module = sys.modules.get(name)
    155          file = self._get_module_file(module)
    156          if file:
    157              date = self._import_dates.get(file)
    158              new_date = None
    159              reload_mod = False
    160              mod_to_pack = False # Module turning into a package? (special case)
    161              try:
    162                  new_date = os.path.getmtime(file)
    163              except:
    164                  self._import_dates.pop(file, None)  # Clean up
    165                  # Handle module changing in package and
    166                  #package changing in module:
    167                  if file.endswith(".py"):
    168                      # Get path without file ext:
    169                      file = os.path.splitext(file)[0]
    170                      reload_mod = os.path.isdir(file) \
    171                        and os.path.isfile(file+self._PACKAGE_PATH_SUFFIX)
    172                      mod_to_pack = reload_mod
    173                  else: # Package turning into module?
    174                      file += ".py"
    175                      reload_mod = os.path.isfile(file)
    176                  if reload_mod:
    177                      new_date = os.path.getmtime(file) # Refresh file date
    178              if reload_mod or not date or new_date > date:
    179                  self._import_dates[file] = new_date
    180              if reload_mod or (date and new_date > date):
    181                  if module not in self._tl._modules_loaded:
    182                      if mod_to_pack:
    183                          # Module turning into a package:
    184                          mod_name = module.__name__
    185                          del sys.modules[mod_name] # Delete the module
    186                          # Reload the module:
    187                          super(_DateTrackerImporter, self).__call__ \
    188                            (mod_name, self._tl.globals, self._tl.locals, [],
    189                             self._tl.level)
    190                      else:
    191                          reload(module)
    192                          self._tl._modules_loaded.add(module)
    193
    194 -    def end(self):
    195          self._tl._modules_loaded = None
    196
    197      @classmethod
    198 -    def _get_module_file(cls, module):
    199          """
    200          Get the absolute path file associated to the module or None.
    201          """
    202
    203          file = getattr(module, "__file__", None)
    204          if file:
    205              # Make path absolute if not:
    206              #file = os.path.join(cls.web2py_path, file)
    207
    208              file = os.path.splitext(file)[0]+".py" # Change .pyc for .py
    209              if file.endswith(cls._PACKAGE_PATH_SUFFIX):
    210                  file = os.path.dirname(file)  # Track dir for packages
    211          return file
    212
    213 -class _Web2pyImporter(_BaseImporter):
    214      """
    215      The standard web2py importer. Like the standard Python importer but it
    216      tries to transform import statements as something like
    217      "import applications.app_name.modules.x". If the import failed, fall back
    218      on _BaseImporter.
    219      """
    220
    221      _RE_ESCAPED_PATH_SEP = re.escape(os.path.sep)  # os.path.sep escaped for re
    222
    223 -    def __init__(self, web2py_path):
    224          """
    225          @param web2py_path: The absolute path of the web2py installation.
    226          """
    227
    228          global DEBUG
    229          super(_Web2pyImporter, self).__init__()
    230          self.web2py_path =  web2py_path
    231          self.__web2py_path_os_path_sep = self.web2py_path+os.path.sep
    232          self.__web2py_path_os_path_sep_len = len(self.__web2py_path_os_path_sep)
    233          self.__RE_APP_DIR = re.compile(
    234            self._RE_ESCAPED_PATH_SEP.join( \
    235            ( \
    236              #"^" + re.escape(web2py_path),   # Not working with Python 2.5
    237              "^(" + "applications",
    238              "[^",
    239              "]+)",
    240              "",
    241            ) ))
    242
    243 -    def _matchAppDir(self, file_path):
    244          """
    245          Does the file in a directory inside the "applications" directory?
    246          """
    247
    248          if file_path.startswith(self.__web2py_path_os_path_sep):
    249              file_path = file_path[self.__web2py_path_os_path_sep_len:]
    250              return self.__RE_APP_DIR.match(file_path)
    251          return False
    252
    253 -    def __call__(self, name, globals=None, locals=None,
    254                   fromlist=None, level=-1):
    255          """
    256          The import method itself.
    257          """
    258
    259          globals = globals or {}
    260          locals = locals or {}
    261          fromlist = fromlist or []
    262
    263          self.begin()
    264          #try:
    265          # if not relative and not from applications:
    266          if not name.startswith(".") and level <= 0 \
    267                      and not name.startswith("applications.") \
    268                      and isinstance(globals, dict):
    269              # Get the name of the file do the import
    270              caller_file_name = os.path.join(self.web2py_path, \
    271                                              globals.get("__file__", ""))
    272              # Is the path in an application directory?
    273              match_app_dir = self._matchAppDir(caller_file_name)
    274              if match_app_dir:
    275                  try:
    276                      # Get the prefix to add for the import
    277                      # (like applications.app_name.modules):
    278                      modules_prefix = \
    279                          ".".join((match_app_dir.group(1). \
    280                          replace(os.path.sep, "."), "modules"))
    281                      if not fromlist:
    282                          # import like "import x" or "import x.y"
    283                          return self.__import__dot(modules_prefix, name,
    284                              globals, locals, fromlist, level)
    285                      else:
    286                          # import like "from x import a, b, ..."
    287                          return super(_Web2pyImporter, self) \
    288                              .__call__(modules_prefix+"."+name,
    289                                      globals, locals, fromlist, level)
    290                  except ImportError:
    291                      pass
    292          return super(_Web2pyImporter, self).__call__(name, globals, locals,
    293                                                      fromlist, level)
    294          #except Exception, e:
    295          #    raise e  # Don't hide something that went wrong
    296          #finally:
    297          self.end()
    298
    299 -    def __import__dot(self, prefix, name, globals, locals, fromlist,
    300                        level):
    301          """
    302          Here we will import x.y.z as many imports like:
    303          from applications.app_name.modules import x
    304          from applications.app_name.modules.x import y
    305          from applications.app_name.modules.x.y import z.
    306          x will be the module returned.
    307          """
    308
    309          result = None
    310          for name in name.split("."):
    311              new_mod = super(_Web2pyImporter, self).__call__(prefix, globals,
    312                                                          locals, [name], level)
    313              try:
    314                  result = result or new_mod.__dict__[name]
    315              except KeyError:
    316                  raise ImportError()
    317              prefix += "." + name
    318          return result
    319
    320 -class _Web2pyDateTrackerImporter(_Web2pyImporter, _DateTrackerImporter):
    321      """
    322      Like _Web2pyImporter but using a _DateTrackerImporter.
    323      """
    324