X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/17f3e5304f4756799dfc0b777f2f0374b0d0b0cb..42f8298f6f42d5d63bb3caf65682b7d9d9f8b702:/wxPython/wxversion/wxversion.py diff --git a/wxPython/wxversion/wxversion.py b/wxPython/wxversion/wxversion.py index dec5b4ebe7..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,24 +34,53 @@ 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 VersionError(Exception): + pass +#---------------------------------------------------------------------- -def require(versions): +def select(versions): """ - Search for a wxPython installation that matches version. - - :param version: Specifies the version to look for, it can either be - a sting or a list of strings. Each string is - compared to the installed wxPythons and the best - match is added to the sys.path, allowing an 'import - wx' to find that 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 + string is compared to the installed wxPythons + and the best match is inserted into the + sys.path, allowing an 'import wx' to find that + version. The version string is composed of the dotted version number (at least 2 of the 4 components) @@ -58,32 +93,140 @@ def require(versions): is increased for every specified optional component that is specified and that matches. """ - assert not sys.modules.has_key('wx') and not sys.modules.has_key('wxPython'), \ - "wxversion.require() must be called before wxPython is imported" + if type(versions) == str: + versions = [versions] + + global _selected + if _selected is not None: + # A version was previously selected, ensure that it matches + # this new request + for ver in versions: + if _selected.Score(_wxPackageInfo(ver)) > 0: + return + # otherwise, raise an exception + 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 sys.modules.has_key('wx') or sys.modules.has_key('wxPython'): + 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. + 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 - bestScore = 0 + minv = _wxPackageInfo(minVersion) + defaultPath = _find_default() + if defaultPath: + defv = _wxPackageInfo(defaultPath, True) + if defv >= minv: + bestMatch = defv + + if bestMatch is None: + 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] - - packages = _find_installed() - for pkg in packages: + 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 - - assert bestMatch is not None, \ - "Required version of wxPython not found" - - sys.path.insert(0, bestMatch.pathname) - - + return bestMatch _pattern = "wx-[0-9].*" -def _find_installed(): +def _find_installed(removeExisting=False): installed = [] toRemove = [] for pth in sys.path: @@ -114,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 @@ -135,41 +308,64 @@ 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[:] - require(version) + + #test + select(version) print "Asked for %s:\t got: %s" % (version, sys.path[0]) pprint.pprint(sys.path) print + + # reset sys.path = savepath[:] + global _selected + _selected = None # make some test dirs @@ -190,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") @@ -201,14 +405,21 @@ if __name__ == '__main__': # available 2.4. Should it give an error instead? I don't think so... test("2.4-unicode") + # Try asking for multiple versions + test(["2.6", "2.5.3", "2.5.2-gtk2"]) + try: # expecting an error on this one test("2.6") - except AssertionError: - print "Asked for 2.6:\t got: Assertion" + except VersionError, e: + print "Asked for 2.6:\t got Exception:", e - # Try asking for multiple versions - test(["2.6", "2.5.3", "2.5.2-gtk2"]) + # check for exception when incompatible versions are requested + try: + select("2.4") + select("2.5") + except VersionError, e: + print "Asked for incompatible versions, got Exception:", e # cleanup for name in names: