X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/4f60dce5d49d3a5f38d93c867bd3527fe60e1aec..f78e4942ec0f3f6abd2992502e2d6c5c2e8e55f6:/wxPython/wxversion/wxversion.py?ds=sidebyside diff --git a/wxPython/wxversion/wxversion.py b/wxPython/wxversion/wxversion.py index f7ad4324c5..b024966706 100644 --- a/wxPython/wxversion/wxversion.py +++ b/wxPython/wxversion/wxversion.py @@ -15,10 +15,16 @@ """ If you have more than one version of wxPython installed this module allows your application to choose which version of wxPython will be -imported when it does 'import wx'. You use it like this: +imported when it does 'import wx'. You use it like this:: import wxversion - wxversion.require('2.4') + wxversion.select('2.4') + import wx + +Or additional build options can also be selected, like this:: + + import wxversion + wxversion.select('2.5.3-unicode') import wx Of course the default wxPython version can also be controlled by @@ -28,21 +34,46 @@ selection itself rather than depend on the user to setup the environment correctly. It works by searching the sys.path for directories matching wx-* and -then comparing them to what was passed to the require function. If a +then comparing them to what was passed to the select function. If a match is found then that path is inserted into sys.path. + +NOTE: If you are making a 'bundle' of your application with a tool +like py2exe then you should *not* use the wxversion module since it +looks at the filesystem for the directories on sys.path, it will fail +in a bundled environment. Instead you should simply ensure that the +version of wxPython that you want is found by default on the sys.path +when making the bundled version by setting PYTHONPATH. Then that +version will be included in your bundle and your app will work as +expected. Py2exe and the others usually have a way to tell at runtime +if they are running from a bundle or running raw, so you can check +that and only use wxversion if needed. For example, for py2exe:: + + if not hasattr(sys, 'frozen'): + import wxversion + wxversion.select('2.5') + import wx + +More documentation on wxversion and multi-version installs can be +found at: http://wiki.wxpython.org/index.cgi/MultiVersionInstalls + """ import sys, os, glob, fnmatch _selected = None -class wxversionError(Exception): +class VersionError(Exception): pass +#---------------------------------------------------------------------- -def require(versions): +def select(versions): """ - Search for a wxPython installation that matches version. + Search for a wxPython installation that matches version. If one + is found then sys.path is modified so that version will be + imported with a 'import wx', otherwise a VersionError exception is + raised. This funciton should only be caled once at the begining + of the application before wxPython is imported. :param version: Specifies the version to look for, it can either be a string or a list of strings. Each @@ -62,8 +93,6 @@ def require(versions): is increased for every specified optional component that is specified and that matches. """ - bestMatch = None - bestScore = 0 if type(versions) == str: versions = [versions] @@ -75,33 +104,129 @@ def require(versions): if _selected.Score(_wxPackageInfo(ver)) > 0: return # otherwise, raise an exception - raise wxversionError("A previously selected wx version does not match the new request.") + raise VersionError("A previously selected wx version does not match the new request.") - # If we get here then this is the first time wxversion is used. - # Ensure that wxPython hasn't been imported yet. + # If we get here then this is the first time wxversion is used, + # ensure that wxPython hasn't been imported yet. if sys.modules.has_key('wx') or sys.modules.has_key('wxPython'): - raise wxversionError("wxversion.require() must be called before wxPython is imported") - + raise VersionError("wxversion.select() must be called before wxPython is imported") + # Look for a matching version and manipulate the sys.path as # needed to allow it to be imported. - packages = _find_installed() - for pkg in packages: - for ver in versions: - score = pkg.Score(_wxPackageInfo(ver)) - if score > bestScore: - bestMatch = pkg - bestScore = score + installed = _find_installed(True) + bestMatch = _get_best_match(installed, versions) + + if bestMatch is None: + raise VersionError("Requested version of wxPython not found") + + sys.path.insert(0, bestMatch.pathname) + _selected = bestMatch + +#---------------------------------------------------------------------- + +UPDATE_URL = "http://wxPython.org/" +#UPDATE_URL = "http://sourceforge.net/project/showfiles.php?group_id=10718" + + +def ensureMinimal(minVersion): + """ + Checks to see if the default version of wxPython is greater-than + or equal to `minVersion`. If not then it will try to find an + installed version that is >= minVersion. If none are available + then a message is displayed that will inform the user and will + offer to open their web browser to the wxPython downloads page, + and will then exit the application. + """ + assert type(minVersion) == str + + # ensure that wxPython hasn't been imported yet. + if sys.modules.has_key('wx') or sys.modules.has_key('wxPython'): + raise VersionError("wxversion.ensureMinimal() must be called before wxPython is imported") + + bestMatch = None + minv = _wxPackageInfo(minVersion) + defaultPath = _find_default() + if defaultPath: + defv = _wxPackageInfo(defaultPath, True) + if defv >= minv: + bestMatch = defv if bestMatch is None: - raise wxversionError("Requested version of wxPython not found") + installed = _find_installed() + if installed: + # The list is in reverse sorted order, so if the first one is + # big enough then choose it + if installed[0] >= minv: + bestMatch = installed[0] + + if bestMatch is None: + import wx, webbrowser + versions = "\n".join([" "+ver for ver in getInstalled()]) + app = wx.PySimpleApp() + result = wx.MessageBox("This application requires a version of wxPython " + "greater than or equal to %s, but a matching version " + "was not found.\n\n" + "You currently have these version(s) installed:\n%s\n\n" + "Would you like to download a new version of wxPython?\n" + % (minVersion, versions), + "wxPython Upgrade Needed", style=wx.YES_NO) + if result == wx.YES: + webbrowser.open(UPDATE_URL) + app.MainLoop() + sys.exit() sys.path.insert(0, bestMatch.pathname) _selected = bestMatch +#---------------------------------------------------------------------- + +def checkInstalled(versions): + """ + Check if there is a version of wxPython installed that matches one + of the versions given. Returns True if so, False if not. This + can be used to determine if calling `select` will succeed or not. + + :param version: Same as in `select`, either a string or a list + of strings specifying the version(s) to check + for. + """ + + if type(versions) == str: + versions = [versions] + installed = _find_installed() + bestMatch = _get_best_match(installed, versions) + return bestMatch is not None + +#---------------------------------------------------------------------- + +def getInstalled(): + """ + Returns a list of strings representing the installed wxPython + versions that are found on the system. + """ + installed = _find_installed() + return [os.path.basename(p.pathname)[3:] for p in installed] + + + +#---------------------------------------------------------------------- +# private helpers... + +def _get_best_match(installed, versions): + bestMatch = None + bestScore = 0 + for pkg in installed: + for ver in versions: + score = pkg.Score(_wxPackageInfo(ver)) + if score > bestScore: + bestMatch = pkg + bestScore = score + return bestMatch + _pattern = "wx-[0-9].*" -def _find_installed(): +def _find_installed(removeExisting=False): installed = [] toRemove = [] for pth in sys.path: @@ -132,14 +257,44 @@ def _find_installed(): continue installed.append(_wxPackageInfo(name, True)) - for rem in toRemove: - del sys.path[sys.path.index(rem)] + if removeExisting: + for rem in toRemove: + del sys.path[sys.path.index(rem)] installed.sort() installed.reverse() return installed +# Scan the sys.path looking for either a directory matching _pattern, +# or a wx.pth file +def _find_default(): + for pth in sys.path: + # empty means to look in the current dir + if not pth: + pth = '.' + + # skip it if it's not a package dir + if not os.path.isdir(pth): + continue + + # does it match the pattern? + base = os.path.basename(pth) + if fnmatch.fnmatchcase(base, _pattern): + return pth + + for pth in sys.path: + if not pth: + pth = '.' + if not os.path.isdir(pth): + continue + if os.path.exists(os.path.join(pth, 'wx.pth')): + base = open(os.path.join(pth, 'wx.pth')).read() + return os.path.join(pth, base) + + return None + + class _wxPackageInfo(object): def __init__(self, pathname, stripFirst=False): self.pathname = pathname @@ -153,43 +308,59 @@ class _wxPackageInfo(object): def Score(self, other): score = 0 - # whatever version components given in other must match exactly - if len(self.version) > len(other.version): - v = self.version[:len(other.version)] - else: - v = self.version - if v != other.version: - return 0 + + # whatever number of version components given in other must + # match exactly + minlen = min(len(self.version), len(other.version)) + if self.version[:minlen] != other.version[:minlen]: + return 0 score += 1 + for opt in other.options: if opt in self.options: score += 1 return score - # TODO: factor self.options into the sort order? + def __lt__(self, other): - return self.version < other.version + return self.version < other.version or \ + (self.version == other.version and self.options < other.options) + def __le__(self, other): + return self.version <= other.version or \ + (self.version == other.version and self.options <= other.options) + def __gt__(self, other): - return self.version > other.version + return self.version > other.version or \ + (self.version == other.version and self.options > other.options) + def __ge__(self, other): + return self.version >= other.version or \ + (self.version == other.version and self.options >= other.options) + def __eq__(self, other): - return self.version == other.version + return self.version == other.version and self.options == other.options - +#---------------------------------------------------------------------- if __name__ == '__main__': import pprint + + #ensureMinimal('2.5') + #pprint.pprint(sys.path) + #sys.exit() + + def test(version): # setup savepath = sys.path[:] #test - require(version) + select(version) print "Asked for %s:\t got: %s" % (version, sys.path[0]) - #pprint.pprint(sys.path) - #print + pprint.pprint(sys.path) + print # reset sys.path = savepath[:] @@ -215,6 +386,14 @@ if __name__ == '__main__': # now run some tests + pprint.pprint( getInstalled()) + print checkInstalled("2.4") + print checkInstalled("2.5-unicode") + print checkInstalled("2.99-bogus") + print "Current sys.path:" + pprint.pprint(sys.path) + print + test("2.4") test("2.5") test("2.5-gtk2") @@ -232,14 +411,14 @@ if __name__ == '__main__': try: # expecting an error on this one test("2.6") - except wxversionError, e: + except VersionError, e: print "Asked for 2.6:\t got Exception:", e # check for exception when incompatible versions are requested try: - require("2.4") - require("2.5") - except wxversionError, e: + select("2.4") + select("2.5") + except VersionError, e: print "Asked for incompatible versions, got Exception:", e # cleanup