What is VersionSelection?

VersionSelection is the process whereby the desired version of [wxPython] will be used out of multiple versions installed There are a number of proposals as to how to do this. One possible solution is included below. Some involve changes to the [www.python.org Python] language, but this page (for now) just deals with what will work at the moment. For almost all of these, you have to install wx into non-standard locations, so that is not currently listed as a Disadvantage.

Requirements

Basic Goals:

More detail:

Techniques

.pth files

Change the path inside a .pth file.

Advantages:

Disadvantages:

import as

Install into wxNNN and then say:

import wx252 as wx

Advantages:

Disadvantages:

Create a symbolic link to the version you want to use

Advantages:

Disadvantages:

environment variables

This involves having an environment variable set to indicate the desired version A module can then be loaded, either automatically (using sitecustomize) or manually, and the desired version added to the path and imported

Advantages:

Disadvantages:

version number in path

This means that wx would be installed into wxPython-2.5.1.5 or wxPython-24 (depending on the scheme chosen). This could be combined with some of the above methods

Issues:

A Solution

This is one approach which combines some of the above ideas. Basically each version of wx is installed into a different directory, e.g. wxPython-2.5.1.5 (including the full version number). These directories don't have to be in any particular location. A path file is created for each wx directory and placed on the Python path. This means that without version selection, a single wx version will work as normal, and with multiple versions, one of them will be chosen (whichever ends up coming first in the Python path).

A module (currently called wxselect) reads all the path files and is able to select a version of wx and edit sys.path so that that version is seen by other programs. This selection can be done either by setting environment variables, or by calling a function in wxselect from the main program. One approach I have found helpful is to import wxselect from site-customize.py which means it is always loaded.

Environment variables used are WXPYTHON_PATH (which sets the path of the desired version directly), WXPYTHON_VERSION (chooses the latest version starting with this string, so 2.4 could select 2.4.2.4, 2.5.1 could select 2.5.1.5 etc), WXPYTHON_MINVERSION (chooses the latest version greater than this string, so 2.4 would select 2.5.1.5 if that was the latest available, 2.5 would cause an error if 2.4.2.4 was the latest version).

Choosing the version from the source code of the main application can be done as follows:

import wxselect
wxselect.wxVersionFinder.setpath(version="2.5")
import wx

setpath takes parameters corresponding to the environment variables - path for WXPYTHONPATH, version for WXPYTHON_VERSION, minversion for WXPYTHON_MINVERSION

Source code

Here is the full source code for wxselect.py

import os
import sys
import glob

"""wxselect selects an appropriate wxPython module and adds it to sys.path
Version selection is implemented with environment variables"
Order of precedence is:
  WXPYTHON_PATH
  WXPYTHON_VERSION (looks for a version starting with this - 2.4 or 2.4.2.4 are valid)
  WXPYTHON_MINVERSION (requires at least this version)
Otherwise the latest available version is used
"""

class EnvConfig:
  """reads environment variables of the form MODULE_KEY and stores them as self.key"""
  def __init__(self, modulename, keys):
    for key in keys:
      setattr(self, key, os.getenv("%s_%s" % (modulename.upper(), key.upper())))

class VersionFinder:
  """Finds Versions of a module using module-x.y.z directory names and selects best match for environment variables"""
  keys = ("minversion", "version", "path", "pythonpath")
  def __init__(self, modulename, versionimportlist = None, versionattrlist = ["ver", "version", "VERSION", "VERSION_STRING"]):
    """construct a VersionFinder for the given modulename"""
    self.modulename = modulename
    if versionimportlist:
      self.versionimportlist = versionimportlist
    else:
      self.versionimportlist = [os.path.join(self.modulename, "__version__.py")]
    self.versionattrlist = versionattrlist
    self.findversions()

  def findversions(self):
    """finds all versions of this module by looking at module-x.y.z directories in the Python Path"""
    self.versions = {}
    for path in sys.path:
      filenames = glob.glob(os.path.join(path, '%s-*' % self.modulename))
      for filename in filenames:
        if os.path.isfile(filename) and filename.lower().endswith(os.extsep + "pth"):
          versionname = os.path.splitext(os.path.basename(filename))[0]
          versionpaths = []
          for versiondir in open(filename):
            versionpaths.extend(self.readversionpath(versiondir.strip()))
        elif os.path.isdir(filename):
          versionname = os.path.basename(filename)
          versionpaths = self.readversionpath(filename)
        else:
          continue
        version = versionname[len("%s-" % self.modulename):]
        if version not in self.versions:
          self.versions[version] = versionpaths
    return self.versions

  def readversionpath(self, versiondir):
    """reads any .pth files in the versiondir and returns the path required for the version"""
    versionpaths = [versiondir]
    versionpthfiles = glob.glob(os.path.join(versiondir, '*.pth'))
    for pthfile in versionpthfiles:
      for line in open(pthfile, "r"):
        versionpath = line.strip()
        if not versionpath: continue
        if not os.path.isabs(versionpath):
          versionpath = os.path.join(os.path.dirname(versiondir), versionpath)
        versionpaths.append(versionpath)
    return versionpaths

  def readpathversion(self, versionpath):
    """reads the module version from the given path"""
    import imp
    for versionimportpath in self.versionimportlist:
      versionfilename = os.path.join(versionpath, versionimportpath)
      if os.path.isfile(versionfilename):
        versionmodule = imp.load_source(os.path.basename(versionfilename), versionfilename, open(versionfilename, 'r'))
        if versionmodule is not None:
          for versionattrname in self.versionattrlist:
            version = getattr(versionmodule, versionattrname, None)
            if version is not None:
              return version
    return None

  def getversionpath(self, version):
    """looks up the pathsep-joined path for the given version"""
    return os.path.pathsep.join(self.versions[version])

  def listversions(self):
    """lists known versions"""
    return self.versions.keys()

  def getbestversion(self, possibleversions):
    """finds the best version out of the possibilities"""
    if possibleversions:
      return max(possibleversions)

  def getconfig(self, path=None, version=None, minversion=None):
    """reads the environment variables and intelligently chooses version and path"""
    config = EnvConfig(self.modulename, self.keys)
    if path:
      config.path = path
    if version:
      config.version = version
    if minversion:
      config.minversion = minversion
    if config.path:
      config.version = self.readpathversion(config.path)
    else:
      if config.version:
        possibleversions = [version for version in self.listversions() if version.startswith(config.version)]
      elif config.minversion:
        possibleversions = [version for version in self.listversions() if version >= config.minversion]
      else:
        possibleversions = self.listversions()
      config.version = self.getbestversion(possibleversions)
      if config.version:
        config.path = self.getversionpath(config.version)
    return config

  def setpath(self, path=None, version=None, minversion=None):
    """removes other versions from the path and appends the selected path"""
    allpaths = []
    map(allpaths.extend, self.versions.values())
    self.removefrompath(allpaths)
    config = self.getconfig(path, version, minversion)
    self.appendtopath(config.path)

  def appendtopath(self, paths):
    """takes a pathsep-separated path list and adds elements to the Python path at the end"""
    if paths:
      pathlist = paths.split(os.path.pathsep)
      pathlist = [path for path in pathlist if path and os.path.isdir(path)]
      sys.path.extend(pathlist)

  def prependtopath(self, paths):
    """takes a pathsep-separated path list and adds elements to the Python path at the beginning"""
    if paths:
      pathlist = paths.split(os.path.pathsep)
      pathlist = [path for path in pathlist if path and os.path.isdir(path)]
      sys.path = pathlist + sys.path

  def removefrompath(self, pathlist):
    """removes all known versions from the PythonPath"""
    def normalize(path):
      return os.path.normcase(os.path.normpath(os.path.abspath(path)))
    if pathlist:
      pathlist = [normalize(path) for path in pathlist if path and os.path.isdir(path)]
      sys.path = [path for path in sys.path if normalize(path) not in pathlist]

wx25versionfile = os.path.join("wx", "__version__.py")
wx25versionattr = "VERSION_STRING"
wx24versionfile = os.path.join("wxPython", "__version__.py")
wx24versionattr = "wxVERSION_STRING"
wxversionimportlist = [wx25versionfile, wx24versionfile]
wxversionattrlist = [wx25versionattr, wx24versionattr]
wxVersionFinder = VersionFinder("wxPython", versionimportlist = wxversionimportlist, versionattrlist = wxversionattrlist)

if __name__ == "__main__":
  print "wxPython version selector"
  print "available versions:"
  for version, path in wxVersionFinder.versions.iteritems():
    print "%s: %s" % (version, path)
  print
  config = wxVersionFinder.getconfig()
  print "selected: %s in %s" % (config.version, config.path)
else:
  wxVersionFinder.setpath()
  # wxVersionFinder.appendtopath(wxVersionFinder.getconfig().path)

VersionSelection (last edited 2013-05-02 15:34:45 by c-98-246-90-205)

NOTE: To edit pages in this wiki you must be a member of the TrustedEditorsGroup.