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 _web2py_importer:
     15              return  # Already 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={}, locals={}, fromlist=[], level=-1):
     70          """
     71          The import method itself.
     72          """
     73          return _STANDARD_PYTHON_IMPORTER(name, globals, locals, fromlist,
     74                                           level)
     75
     76 -    def end(self):
     77          """
     78          Needed for clean up.
     79          """
     80
     81
     82 -class _DateTrackerImporter(_BaseImporter):
     83      """
     84      An importer tracking the date of the module files and reloading them when
     85      they have changed.
     86      """
     87
     88      _PACKAGE_PATH_SUFFIX = os.path.sep+"__init__.py"
     89
     90 -    def __init__(self):
     91          super(_DateTrackerImporter, self).__init__()
     92          self._import_dates = {} # Import dates of the files of the modules
     93          # Avoid reloading cause by file modifications of reload:
     94          self._tl = threading.local()
     95          self._tl._modules_loaded = None
     96
     97 -    def begin(self):
     98          self._tl._modules_loaded = set()
     99
    100 -    def __call__(self, name, globals={}, locals={}, fromlist=[], level=-1):
    101          """
    102          The import method itself.
    103          """
    104
    105          call_begin_end = self._tl._modules_loaded == None
    106          if call_begin_end:
    107              self.begin()
    108
    109          try:
    110              self._tl.globals = globals
    111              self._tl.locals = locals
    112              self._tl.level = level
    113
    114              # Check the date and reload if needed:
    115              self._update_dates(name, fromlist)
    116
    117              # Try to load the module and update the dates if it works:
    118              result = super(_DateTrackerImporter, self) \
    119                .__call__(name, globals, locals, fromlist, level)
    120              # Module maybe loaded for the 1st time so we need to set the date
    121              self._update_dates(name, fromlist)
    122              return result
    123          except Exception, e:
    124              raise e  # Don't hide something that went wrong
    125          finally:
    126              if call_begin_end:
    127                  self.end()
    128
    129 -    def _update_dates(self, name, fromlist):
    130          """
    131          Update all the dates associated to the statement import. A single
    132          import statement may import many modules.
    133          """
    134
    135          self._reload_check(name)
    136          if fromlist:
    137              for fromlist_name in fromlist:
    138                  self._reload_check("%s.%s" % (name, fromlist_name))
    139
    140 -    def _reload_check(self, name):
    141          """
    142          Update the date associated to the module and reload the module if
    143          the file has changed.
    144          """
    145
    146          module = sys.modules.get(name)
    147          file = self._get_module_file(module)
    148          if file:
    149              date = self._import_dates.get(file)
    150              new_date = None
    151              reload_mod = False
    152              mod_to_pack = False # Module turning into a package? (special case)
    153              try:
    154                  new_date = os.path.getmtime(file)
    155              except:
    156                  self._import_dates.pop(file, None)  # Clean up
    157                  # Handle module changing in package and
    158                  #package changing in module:
    159                  if file.endswith(".py"):
    160                      # Get path without file ext:
    161                      file = os.path.splitext(file)[0]
    162                      reload_mod = os.path.isdir(file) \
    163                        and os.path.isfile(file+self._PACKAGE_PATH_SUFFIX)
    164                      mod_to_pack = reload_mod
    165                  else: # Package turning into module?
    166                      file += ".py"
    167                      reload_mod = os.path.isfile(file)
    168                  if reload_mod:
    169                      new_date = os.path.getmtime(file) # Refresh file date
    170              if reload_mod or not date or new_date > date:
    171                  self._import_dates[file] = new_date
    172              if reload_mod or (date and new_date > date):
    173                  if module not in self._tl._modules_loaded:
    174                      if mod_to_pack:
    175                          # Module turning into a package:
    176                          mod_name = module.__name__
    177                          del sys.modules[mod_name] # Delete the module
    178                          # Reload the module:
    179                          super(_DateTrackerImporter, self).__call__ \
    180                            (mod_name, self._tl.globals, self._tl.locals, [],
    181                             self._tl.level)
    182                      else:
    183                          reload(module)
    184                          self._tl._modules_loaded.add(module)
    185
    186 -    def end(self):
    187          self._tl._modules_loaded = None
    188
    189      @classmethod
    190 -    def _get_module_file(cls, module):
    191          """
    192          Get the absolute path file associated to the module or None.
    193          """
    194
    195          file = getattr(module, "__file__", None)
    196          if file:
    197              # Make path absolute if not:
    198              #file = os.path.join(cls.web2py_path, file)
    199
    200              file = os.path.splitext(file)[0]+".py" # Change .pyc for .py
    201              if file.endswith(cls._PACKAGE_PATH_SUFFIX):
    202                  file = os.path.dirname(file)  # Track dir for packages
    203          return file
    204
    205 -class _Web2pyImporter(_BaseImporter):
    206      """
    207      The standard web2py importer. Like the standard Python importer but it
    208      tries to transform import statements as something like
    209      "import applications.app_name.modules.x". If the import failed, fall back
    210      on _BaseImporter.
    211      """
    212
    213      _RE_ESCAPED_PATH_SEP = re.escape(os.path.sep)  # os.path.sep escaped for re
    214
    215 -    def __init__(self, web2py_path):
    216          """
    217          @param web2py_path: The absolute path of the web2py installation.
    218          """
    219
    220          global DEBUG
    221          super(_Web2pyImporter, self).__init__()
    222          self.web2py_path =  web2py_path
    223          self.__web2py_path_os_path_sep = self.web2py_path+os.path.sep
    224          self.__web2py_path_os_path_sep_len = len(self.__web2py_path_os_path_sep)
    225          self.__RE_APP_DIR = re.compile(
    226            self._RE_ESCAPED_PATH_SEP.join( \
    227            ( \
    228              #"^" + re.escape(web2py_path),   # Not working with Python 2.5
    229              "^(" + "applications",
    230              "[^",
    231              "]+)",
    232              "",
    233            ) ))
    234
    235 -    def _matchAppDir(self, file_path):
    236          """
    237          Does the file in a directory inside the "applications" directory?
    238          """
    239
    240          if file_path.startswith(self.__web2py_path_os_path_sep):
    241              file_path = file_path[self.__web2py_path_os_path_sep_len:]
    242              return self.__RE_APP_DIR.match(file_path)
    243          return False
    244
    245 -    def __call__(self, name, globals={}, locals={}, fromlist=[], level=-1):
    246          """
    247          The import method itself.
    248          """
    249
    250          self.begin()
    251          #try:
    252          # if not relative and not from applications:
    253          if not name.startswith(".") and level <= 0 \
    254                      and not name.startswith("applications.") \
    255                      and isinstance(globals, dict):
    256              # Get the name of the file do the import
    257              caller_file_name = os.path.join(self.web2py_path, \
    258                                              globals.get("__file__", ""))
    259              # Is the path in an application directory?
    260              match_app_dir = self._matchAppDir(caller_file_name)
    261              if match_app_dir:
    262                  try:
    263                      # Get the prefix to add for the import
    264                      # (like applications.app_name.modules):
    265                      modules_prefix = \
    266                          ".".join((match_app_dir.group(1). \
    267                          replace(os.path.sep, "."), "modules"))
    268                      if not fromlist:
    269                          # import like "import x" or "import x.y"
    270                          return self.__import__dot(modules_prefix, name,
    271                              globals, locals, fromlist, level)
    272                      else:
    273                          # import like "from x import a, b, ..."
    274                          return super(_Web2pyImporter, self) \
    275                              .__call__(modules_prefix+"."+name,
    276                                      globals, locals, fromlist, level)
    277                  except ImportError:
    278                      pass
    279          return super(_Web2pyImporter, self).__call__(name, globals, locals,
    280                                                      fromlist, level)
    281          #except Exception, e:
    282          #    raise e  # Don't hide something that went wrong
    283          #finally:
    284          self.end()
    285
    286 -    def __import__dot(self, prefix, name, globals, locals, fromlist,
    287                        level):
    288          """
    289          Here we will import x.y.z as many imports like:
    290          from applications.app_name.modules import x
    291          from applications.app_name.modules.x import y
    292          from applications.app_name.modules.x.y import z.
    293          x will be the module returned.
    294          """
    295
    296          result = None
    297          for name in name.split("."):
    298              new_mod = super(_Web2pyImporter, self).__call__(prefix, globals,
    299                                                          locals, [name], level)
    300              try:
    301                  result = result or new_mod.__dict__[name]
    302              except KeyError:
    303                  raise ImportError()
    304              prefix += "." + name
    305          return result
    306
    307 -class _Web2pyDateTrackerImporter(_Web2pyImporter, _DateTrackerImporter):
    308      """
    309      Like _Web2pyImporter but using a _DateTrackerImporter.
    310      """
    311