thirdparty/google_appengine/google/appengine/dist/py_zipimport.py
changeset 2273 e4cb9c53db3e
child 2864 2e0b0af889be
equal deleted inserted replaced
2272:26491ee91e33 2273:e4cb9c53db3e
       
     1 #!/usr/bin/env python
       
     2 #
       
     3 # Copyright 2007 Google Inc.
       
     4 #
       
     5 # Licensed under the Apache License, Version 2.0 (the "License");
       
     6 # you may not use this file except in compliance with the License.
       
     7 # You may obtain a copy of the License at
       
     8 #
       
     9 #     http://www.apache.org/licenses/LICENSE-2.0
       
    10 #
       
    11 # Unless required by applicable law or agreed to in writing, software
       
    12 # distributed under the License is distributed on an "AS IS" BASIS,
       
    13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
       
    14 # See the License for the specific language governing permissions and
       
    15 # limitations under the License.
       
    16 #
       
    17 
       
    18 """Pure Python zipfile importer.
       
    19 
       
    20 This approximates the standard zipimport module, which isn't supported
       
    21 by Google App Engine.  See PEP 302 for more information about the API
       
    22 for import hooks.
       
    23 
       
    24 Usage:
       
    25   import py_zipimport
       
    26 
       
    27 As a side effect of importing, the module overrides sys.path_hooks,
       
    28 and also creates an alias 'zipimport' for itself.  When your app is
       
    29 running in Google App Engine production, you don't even need to import
       
    30 it, since this is already done for you.  In the Google App Engine SDK
       
    31 this module is not used; instead, the standard zipimport module is
       
    32 used.
       
    33 """
       
    34 
       
    35 
       
    36 __all__ = ['ZipImportError', 'zipimporter']
       
    37 
       
    38 
       
    39 import os
       
    40 import sys
       
    41 import types
       
    42 import UserDict
       
    43 import zipfile
       
    44 
       
    45 
       
    46 _SEARCH_ORDER = [
       
    47 
       
    48     ('.py', False),
       
    49     ('/__init__.py', True),
       
    50 ]
       
    51 
       
    52 
       
    53 _zipfile_cache = {}
       
    54 
       
    55 
       
    56 class ZipImportError(ImportError):
       
    57   """Exception raised by zipimporter objects."""
       
    58 
       
    59 
       
    60 class zipimporter:
       
    61   """A PEP-302-style importer that can import from a zipfile.
       
    62 
       
    63   Just insert or append this class (not an instance) to sys.path_hooks
       
    64   and you're in business.  Instances satisfy both the 'importer' and
       
    65   'loader' APIs specified in PEP 302.
       
    66   """
       
    67 
       
    68   def __init__(self, path_entry):
       
    69     """Constructor.
       
    70 
       
    71     Args:
       
    72       path_entry: The entry in sys.path.  This should be the name of an
       
    73         existing zipfile possibly with a path separator and a prefix
       
    74         path within the archive appended, e.g. /x/django.zip or
       
    75         /x/django.zip/foo/bar.
       
    76 
       
    77     Raises:
       
    78       ZipImportError if the path_entry does not represent a valid
       
    79       zipfile with optional prefix.
       
    80     """
       
    81     archive = path_entry
       
    82     prefix = ''
       
    83     while not os.path.lexists(archive):
       
    84       head, tail = os.path.split(archive)
       
    85       if head == archive:
       
    86         msg = 'Nothing found for %r' % path_entry
       
    87         raise ZipImportError(msg)
       
    88       archive = head
       
    89       prefix = os.path.join(tail, prefix)
       
    90     if not os.path.isfile(archive):
       
    91       msg = 'Non-file %r found for %r' % (archive, path_entry)
       
    92       raise ZipImportError(msg)
       
    93     self.archive = archive
       
    94     self.prefix = os.path.join(prefix, '')
       
    95     self.zipfile = _zipfile_cache.get(archive)
       
    96     if self.zipfile is None:
       
    97       try:
       
    98         self.zipfile = zipfile.ZipFile(self.archive)
       
    99       except (EnvironmentError, zipfile.BadZipfile), err:
       
   100         msg = 'Can\'t open zipfile %s: %s: %s' % (self.archive,
       
   101                                                   err.__class__.__name__, err)
       
   102         import logging
       
   103         logging.warn(msg)
       
   104         raise ZipImportError(msg)
       
   105       else:
       
   106         _zipfile_cache[archive] = self.zipfile
       
   107         import logging
       
   108         logging.info('zipimporter(%r, %r)', archive, prefix)
       
   109 
       
   110   def __repr__(self):
       
   111     """Return a string representation matching zipimport.c."""
       
   112     name = self.archive
       
   113     if self.prefix:
       
   114       name = os.path.join(name, self.prefix)
       
   115     return '<zipimporter object "%s">' % name
       
   116 
       
   117   def _get_info(self, fullmodname):
       
   118     """Internal helper for find_module() and load_module().
       
   119 
       
   120     Args:
       
   121       fullmodname: The dot-separated full module name, e.g. 'django.core.mail'.
       
   122 
       
   123     Returns:
       
   124       A tuple (submodname, is_package, relpath) where:
       
   125         submodname: The final component of the module name, e.g. 'mail'.
       
   126         is_package: A bool indicating whether this is a package.
       
   127         relpath: The path to the module's source code within to the zipfile.
       
   128 
       
   129     Raises:
       
   130       ImportError if the module is not found in the archive.
       
   131     """
       
   132     parts = fullmodname.split('.')
       
   133     submodname = parts[-1]
       
   134     for suffix, is_package in _SEARCH_ORDER:
       
   135       relpath = os.path.join(self.prefix,
       
   136                              submodname + suffix.replace('/', os.sep))
       
   137       try:
       
   138         self.zipfile.getinfo(relpath.replace(os.sep, '/'))
       
   139       except KeyError:
       
   140         pass
       
   141       else:
       
   142         return submodname, is_package, relpath
       
   143     msg = ('Can\'t find module %s in zipfile %s with prefix %r' %
       
   144            (fullmodname, self.archive, self.prefix))
       
   145     raise ZipImportError(msg)
       
   146 
       
   147   def _get_source(self, fullmodname):
       
   148     """Internal helper for load_module().
       
   149 
       
   150     Args:
       
   151       fullmodname: The dot-separated full module name, e.g. 'django.core.mail'.
       
   152 
       
   153     Returns:
       
   154       A tuple (submodname, is_package, fullpath, source) where:
       
   155         submodname: The final component of the module name, e.g. 'mail'.
       
   156         is_package: A bool indicating whether this is a package.
       
   157         fullpath: The path to the module's source code including the
       
   158           zipfile's filename.
       
   159         source: The module's source code.
       
   160 
       
   161     Raises:
       
   162       ImportError if the module is not found in the archive.
       
   163     """
       
   164     submodname, is_package, relpath = self._get_info(fullmodname)
       
   165     fullpath = '%s%s%s' % (self.archive, os.sep, relpath)
       
   166     source = self.zipfile.read(relpath.replace(os.sep, '/'))
       
   167     source = source.replace('\r\n', '\n')
       
   168     source = source.replace('\r', '\n')
       
   169     return submodname, is_package, fullpath, source
       
   170 
       
   171   def find_module(self, fullmodname, path=None):
       
   172     """PEP-302-compliant find_module() method.
       
   173 
       
   174     Args:
       
   175       fullmodname: The dot-separated full module name, e.g. 'django.core.mail'.
       
   176       path: Optional and ignored; present for API compatibility only.
       
   177 
       
   178     Returns:
       
   179       None if the module isn't found in the archive; self if it is found.
       
   180     """
       
   181     try:
       
   182       submodname, is_package, relpath = self._get_info(fullmodname)
       
   183     except ImportError:
       
   184       return None
       
   185     else:
       
   186       return self
       
   187 
       
   188   def load_module(self, fullmodname):
       
   189     """PEP-302-compliant load_module() method.
       
   190 
       
   191     Args:
       
   192       fullmodname: The dot-separated full module name, e.g. 'django.core.mail'.
       
   193 
       
   194     Returns:
       
   195       The module object constructed from the source code.
       
   196 
       
   197     Raises:
       
   198       SyntaxError if the module's source code is syntactically incorrect.
       
   199       ImportError if there was a problem accessing the source code.
       
   200       Whatever else can be raised by executing the module's source code.
       
   201     """
       
   202     submodname, is_package, fullpath, source = self._get_source(fullmodname)
       
   203     code = compile(source, fullpath, 'exec')
       
   204     mod = sys.modules.get(fullmodname)
       
   205     try:
       
   206       if mod is None:
       
   207         mod = sys.modules[fullmodname] = types.ModuleType(fullmodname)
       
   208       mod.__loader__ = self
       
   209       mod.__file__ = fullpath
       
   210       mod.__name__ = fullmodname
       
   211       if is_package:
       
   212         mod.__path__ = [os.path.dirname(mod.__file__)]
       
   213       exec code in mod.__dict__
       
   214     except:
       
   215       if fullmodname in sys.modules:
       
   216         del sys.modules[fullmodname]
       
   217       raise
       
   218     return mod
       
   219 
       
   220 
       
   221   def get_data(self, fullpath):
       
   222     """Return (binary) content of a data file in the zipfile."""
       
   223     required_prefix = os.path.join(self.archive, '')
       
   224     if not fullpath.startswith(required_prefix):
       
   225       raise IOError('Path %r doesn\'t start with zipfile name %r' %
       
   226                     (fullpath, required_prefix))
       
   227     relpath = fullpath[len(required_prefix):]
       
   228     try:
       
   229       return self.zipfile.read(relpath)
       
   230     except KeyError:
       
   231       raise IOError('Path %r not found in zipfile %r' %
       
   232                     (relpath, self.archive))
       
   233 
       
   234   def is_package(self, fullmodname):
       
   235     """Return whether a module is a package."""
       
   236     submodname, is_package, relpath = self._get_info(fullmodname)
       
   237     return is_package
       
   238 
       
   239   def get_code(self, fullmodname):
       
   240     """Return bytecode for a module."""
       
   241     submodname, is_package, fullpath, source = self._get_source(fullmodname)
       
   242     return compile(source, fullpath, 'exec')
       
   243 
       
   244   def get_source(self, fullmodname):
       
   245     """Return source code for a module."""
       
   246     submodname, is_package, fullpath, source = self._get_source(fullmodname)
       
   247     return source
       
   248 
       
   249 
       
   250 class ZipFileCache(UserDict.DictMixin):
       
   251   """Helper class to export archive data in _zip_directory_cache.
       
   252 
       
   253   Just take the info from _zipfile_cache and convert it as required.
       
   254   """
       
   255 
       
   256   def __init__(self, archive):
       
   257     _zipfile_cache[archive]
       
   258 
       
   259     self._archive = archive
       
   260 
       
   261   def keys(self):
       
   262     return _zipfile_cache[self._archive].namelist()
       
   263 
       
   264   def __getitem__(self, filename):
       
   265     info = _zipfile_cache[self._archive].getinfo(filename)
       
   266     dt = info.date_time
       
   267     dostime = dt[3] << 11 | dt[4] << 5 | (dt[5] // 2)
       
   268     dosdate = (dt[0] - 1980) << 9 | dt[1] << 5 | dt[2]
       
   269     return (os.path.join(self._archive, info.filename), info.compress_type,
       
   270             info.compress_size, info.file_size, info.header_offset, dostime,
       
   271             dosdate, info.CRC)
       
   272 
       
   273 
       
   274 class ZipDirectoryCache(UserDict.DictMixin):
       
   275   """Helper class to export _zip_directory_cache."""
       
   276 
       
   277   def keys(self):
       
   278     return _zipfile_cache.keys()
       
   279 
       
   280   def __getitem__(self, archive):
       
   281     return ZipFileCache(archive)
       
   282 
       
   283 
       
   284 _zip_directory_cache = ZipDirectoryCache()
       
   285 
       
   286 
       
   287 sys.modules['zipimport'] = sys.modules[__name__]
       
   288 sys.path_hooks[:] = [zipimporter]