thirdparty/google_appengine/google/appengine/dist/_library.py
changeset 2413 d0b7dac5325c
child 2864 2e0b0af889be
equal deleted inserted replaced
2412:c61d96e72e6f 2413:d0b7dac5325c
       
     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 """Code to exist off of google.appengine.dist.
       
    19 
       
    20 Kept in a separate file from the __init__ module for testing purposes.
       
    21 """
       
    22 
       
    23 
       
    24 __all__ = ['use_library']
       
    25 
       
    26 
       
    27 import distutils.version
       
    28 import os
       
    29 import sys
       
    30 
       
    31 server_software = os.getenv('SERVER_SOFTWARE')
       
    32 USING_SDK = not server_software or server_software.startswith('Dev')
       
    33 del server_software
       
    34 
       
    35 if not USING_SDK:
       
    36   import google
       
    37   this_version = os.path.dirname(os.path.dirname(google.__file__))
       
    38   versions = os.path.dirname(this_version)
       
    39   PYTHON_LIB = os.path.dirname(versions)
       
    40   del google, this_version, versions
       
    41 else:
       
    42   PYTHON_LIB = '/base/python_lib'
       
    43 
       
    44 installed = {}
       
    45 
       
    46 
       
    47 def SetAllowedModule(_):
       
    48   pass
       
    49 
       
    50 
       
    51 class UnacceptableVersionError(Exception):
       
    52   """Raised when a version of a package that is unacceptable is requested."""
       
    53   pass
       
    54 
       
    55 
       
    56 def DjangoVersion():
       
    57   """Discover the version of Django installed.
       
    58 
       
    59   Returns:
       
    60     A distutils.version.LooseVersion.
       
    61   """
       
    62   import django
       
    63   return distutils.version.LooseVersion('.'.join(map(str, django.VERSION)))
       
    64 
       
    65 
       
    66 def PylonsVersion():
       
    67   """Discover the version of Pylons installed.
       
    68 
       
    69   Returns:
       
    70     A distutils.version.LooseVersion.
       
    71   """
       
    72   import pylons
       
    73   return distutils.version.LooseVersion(pylons.__version__)
       
    74 
       
    75 
       
    76 PACKAGES = {
       
    77     'django': (DjangoVersion,
       
    78                {'1.0': None, '0.96': None}),
       
    79 
       
    80 
       
    81 
       
    82 
       
    83 
       
    84 
       
    85 
       
    86     '_test': (lambda: distutils.version.LooseVersion('1.0'), {'1.0': None}),
       
    87     '_testpkg': (lambda: distutils.version.LooseVersion('1.0'),
       
    88                  {'1.0': set([('_test', '1.0')])}),
       
    89     }
       
    90 
       
    91 
       
    92 def EqualVersions(version, baseline):
       
    93   """Test that a version is acceptable as compared to the baseline.
       
    94 
       
    95   Meant to be used to compare version numbers as returned by a package itself
       
    96   and not user input.
       
    97 
       
    98   Args:
       
    99     version: distutils.version.LooseVersion.
       
   100         The version that is being checked.
       
   101     baseline: distutils.version.LooseVersion.
       
   102         The version that one hopes version compares equal to.
       
   103 
       
   104   Returns:
       
   105     A bool indicating whether the versions are considered equal.
       
   106   """
       
   107   baseline_tuple = baseline.version
       
   108   truncated_tuple = version.version[:len(baseline_tuple)]
       
   109   if truncated_tuple == baseline_tuple:
       
   110     return True
       
   111   else:
       
   112     return False
       
   113 
       
   114 
       
   115 def AllowInstalledLibrary(name, desired):
       
   116   """Allow the use of a package without performing a version check.
       
   117 
       
   118   Needed to clear a package's dependencies in case the dependencies need to be
       
   119   imported in order to perform a version check. The version check is skipped on
       
   120   the dependencies because the assumption is that the package that triggered
       
   121   the call would not be installed without the proper dependencies (which might
       
   122   be a different version than what the package explicitly requires).
       
   123 
       
   124   Args:
       
   125     name: Name of package.
       
   126     desired: Desired version.
       
   127 
       
   128   Raises:
       
   129     UnacceptableVersion Error if the installed version of a package is
       
   130     unacceptable.
       
   131   """
       
   132   if name == 'django' and desired != '0.96':
       
   133     tail = os.path.join('lib', 'django')
       
   134     sys.path[:] = [dirname
       
   135                    for dirname in sys.path
       
   136                    if not dirname.endswith(tail)]
       
   137   CallSetAllowedModule(name, desired)
       
   138   dependencies = PACKAGES[name][1][desired]
       
   139   if dependencies:
       
   140     for dep_name, dep_version in dependencies:
       
   141       AllowInstalledLibrary(dep_name, dep_version)
       
   142   installed[name] = desired, False
       
   143 
       
   144 
       
   145 def CheckInstalledLibrary(name, desired):
       
   146   """Check that the library and its dependencies are installed.
       
   147 
       
   148   Args:
       
   149     name: Name of the library that should be installed.
       
   150     desired: The desired version.
       
   151 
       
   152   Raises:
       
   153     UnacceptableVersionError if the installed version of a package is
       
   154     unacceptable.
       
   155   """
       
   156   dependencies = PACKAGES[name][1][desired]
       
   157   if dependencies:
       
   158     for dep_name, dep_version in dependencies:
       
   159       AllowInstalledLibrary(dep_name, dep_version)
       
   160   CheckInstalledVersion(name, desired, explicit=True)
       
   161 
       
   162 
       
   163 def CheckInstalledVersion(name, desired, explicit):
       
   164   """Check that the installed version of a package is acceptable.
       
   165 
       
   166   Args:
       
   167     name: Name of package.
       
   168     desired: Desired version string.
       
   169     explicit: Explicitly requested by the user or implicitly because of a
       
   170       dependency.
       
   171 
       
   172   Raises:
       
   173     UnacceptableVersionError if the installed version of a package is
       
   174     unacceptable.
       
   175   """
       
   176   CallSetAllowedModule(name, desired)
       
   177   find_version = PACKAGES[name][0]
       
   178   installed_version = find_version()
       
   179   desired_version = distutils.version.LooseVersion(desired)
       
   180   if not EqualVersions(installed_version, desired_version):
       
   181     raise UnacceptableVersionError(
       
   182         '%s %s was requested, but %s is already in use' %
       
   183         (name, desired_version, installed_version))
       
   184   installed[name] = desired, explicit
       
   185 
       
   186 
       
   187 def CallSetAllowedModule(name, desired):
       
   188   """Helper to call SetAllowedModule(name), after special-casing Django."""
       
   189   if name == 'django' and desired != '0.96':
       
   190     tail = os.path.join('lib', 'django')
       
   191     sys.path[:] = [dirname
       
   192                    for dirname in sys.path
       
   193                    if not dirname.endswith(tail)]
       
   194   SetAllowedModule(name)
       
   195 
       
   196 
       
   197 def CreatePath(name, version):
       
   198   """Create the path to a package."""
       
   199   package_dir = '%s-%s' % (name, version)
       
   200   return os.path.join(PYTHON_LIB, 'versions', 'third_party', package_dir)
       
   201 
       
   202 
       
   203 def RemoveLibrary(name):
       
   204   """Remove a library that has been installed."""
       
   205   installed_version, _ = installed[name]
       
   206   path = CreatePath(name, installed_version)
       
   207   try:
       
   208     sys.path.remove(path)
       
   209   except ValueError:
       
   210     pass
       
   211   del installed[name]
       
   212 
       
   213 
       
   214 def AddLibrary(name, version, explicit):
       
   215   """Add a library to sys.path and 'installed'."""
       
   216   sys.path.insert(1, CreatePath(name, version))
       
   217   installed[name] = version, explicit
       
   218 
       
   219 
       
   220 def InstallLibrary(name, version, explicit=True):
       
   221   """Install a package.
       
   222 
       
   223   If the installation is explicit then the user made the installation request,
       
   224   not a package as a dependency. Explicit installation leads to stricter
       
   225   version checking.
       
   226 
       
   227   Args:
       
   228     name: Name of the requested package (already validated as available).
       
   229     version: The desired version (already validated as available).
       
   230     explicit: Explicitly requested by the user or implicitly because of a
       
   231       dependency.
       
   232   """
       
   233   installed_version, explicitly_installed = installed.get(name, [None] * 2)
       
   234   if name in sys.modules:
       
   235     if explicit:
       
   236       CheckInstalledVersion(name, version, explicit=True)
       
   237     return
       
   238   elif installed_version:
       
   239     if version == installed_version:
       
   240       return
       
   241     if explicit:
       
   242       if explicitly_installed:
       
   243         raise ValueError('%s %s requested, but %s already in use' %
       
   244                          (name, version, installed_version))
       
   245       RemoveLibrary(name)
       
   246     else:
       
   247       version_ob = distutils.version.LooseVersion(version)
       
   248       installed_ob = distutils.version.LooseVersion(installed_version)
       
   249       if version_ob <= installed_ob:
       
   250         return
       
   251       else:
       
   252         RemoveLibrary(name)
       
   253   AddLibrary(name, version, explicit)
       
   254   dep_details = PACKAGES[name][1][version]
       
   255   if not dep_details:
       
   256     return
       
   257   for dep_name, dep_version in dep_details:
       
   258     InstallLibrary(dep_name, dep_version, explicit=False)
       
   259 
       
   260 
       
   261 def use_library(name, version):
       
   262   """Specify a third-party package to use.
       
   263 
       
   264   Args:
       
   265     name: Name of package to use.
       
   266     version: Version of the package to use (string).
       
   267   """
       
   268   if name not in PACKAGES:
       
   269     raise ValueError('%s is not a supported package' % name)
       
   270   versions = PACKAGES[name][1].keys()
       
   271   if version not in versions:
       
   272     raise ValueError('%s is not a supported version for %s; '
       
   273                      'supported versions are %s' % (version, name, versions))
       
   274   if USING_SDK:
       
   275     CheckInstalledLibrary(name, version)
       
   276   else:
       
   277     InstallLibrary(name, version, explicit=True)
       
   278 
       
   279 
       
   280 if not USING_SDK:
       
   281   InstallLibrary('django', '0.96', explicit=False)