]>
git.saurik.com Git - wxWidgets.git/blob - wxPython/wxversion/wxversion.py
   1 #---------------------------------------------------------------------- 
   3 # Purpose:     Allows a wxPython program to search for alternate  
   4 #              installations of the wxPython packages and modify sys.path 
   5 #              so they will be found when "import wx" is done. 
   9 # Created:     24-Sept-2004 
  11 # Copyright:   (c) 2004 by Total Control Software 
  12 # Licence:     wxWindows license 
  13 #---------------------------------------------------------------------- 
  16 If you have more than one version of wxPython installed this module 
  17 allows your application to choose which version of wxPython will be 
  18 imported when it does 'import wx'.  The main function of this module 
  19 is `select` and you use it like this:: 
  22     wxversion.select('2.4') 
  25 Or additional build options can also be selected, although they will 
  26 not be required if they are not installed, like this:: 
  29     wxversion.select('2.5.3-unicode') 
  32 Or you can require an exact match on the build options like this:: 
  35     wxversion.select('2.5.3-unicode', optionsRequired=True) 
  38 Finally you can also specify a collection of versions that are allowed 
  39 by your application, like this:: 
  42     wxversion.select(['2.5.4', '2.5.5', '2.6']) 
  46 Of course the default wxPython version can also be controlled by 
  47 setting PYTHONPATH or by editing the wx.pth path configuration file, 
  48 but using wxversion will allow an application to manage the version 
  49 selection itself rather than depend on the user to setup the 
  50 environment correctly. 
  52 It works by searching the sys.path for directories matching wx-* and 
  53 then comparing them to what was passed to the select function.  If a 
  54 match is found then that path is inserted into sys.path. 
  56 NOTE: If you are making a 'bundle' of your application with a tool 
  57 like py2exe then you should *not* use the wxversion module since it 
  58 looks at the filesystem for the directories on sys.path, it will fail 
  59 in a bundled environment.  Instead you should simply ensure that the 
  60 version of wxPython that you want is found by default on the sys.path 
  61 when making the bundled version by setting PYTHONPATH.  Then that 
  62 version will be included in your bundle and your app will work as 
  63 expected.  Py2exe and the others usually have a way to tell at runtime 
  64 if they are running from a bundle or running raw, so you can check 
  65 that and only use wxversion if needed.  For example, for py2exe:: 
  67     if not hasattr(sys, 'frozen'): 
  69         wxversion.select('2.5') 
  72 More documentation on wxversion and multi-version installs can be 
  73 found at: http://wiki.wxpython.org/index.cgi/MultiVersionInstalls 
  77 import sys
, os
, glob
, fnmatch
 
  81 class VersionError(Exception): 
  84 #---------------------------------------------------------------------- 
  86 def select(versions
, optionsRequired
=False): 
  88     Search for a wxPython installation that matches version.  If one 
  89     is found then sys.path is modified so that version will be 
  90     imported with a 'import wx', otherwise a VersionError exception is 
  91     raised.  This funciton should only be caled once at the begining 
  92     of the application before wxPython is imported. 
  94         :param versions: Specifies the version to look for, it can 
  95             either be a string or a list of strings.  Each string is 
  96             compared to the installed wxPythons and the best match is 
  97             inserted into the sys.path, allowing an 'import wx' to 
 100             The version string is composed of the dotted version 
 101             number (at least 2 of the 4 components) optionally 
 102             followed by hyphen ('-') separated options (wx port, 
 103             unicode/ansi, flavour, etc.)  A match is determined by how 
 104             much of the installed version matches what is given in the 
 105             version parameter.  If the version number components don't 
 106             match then the score is zero, otherwise the score is 
 107             increased for every specified optional component that is 
 108             specified and that matches. 
 110             Please note, however, that it is possible for a match to 
 111             be selected that doesn't exactly match the versions 
 112             requested.  The only component that is required to be 
 113             matched is the version number.  If you need to require a 
 114             match on the other components as well, then please use the 
 115             optional ``optionsRequired`` parameter described next. 
 117         :param optionsRequired: Allows you to specify that the other 
 118              components of the version string (such as the port name 
 119              or character type) are also required to be present for an 
 120              installed version to be considered a match.  Using this 
 121              parameter allows you to change the selection from a soft, 
 122              as close as possible match to a hard, exact match. 
 125     if type(versions
) == str: 
 126         versions 
= [versions
] 
 129     if _selected 
is not None: 
 130         # A version was previously selected, ensure that it matches 
 133             if _selected
.Score(_wxPackageInfo(ver
), optionsRequired
) > 0: 
 135         # otherwise, raise an exception 
 136         raise VersionError("A previously selected wx version does not match the new request.") 
 138     # If we get here then this is the first time wxversion is used,  
 139     # ensure that wxPython hasn't been imported yet. 
 140     if sys
.modules
.has_key('wx') or sys
.modules
.has_key('wxPython'): 
 141         raise VersionError("wxversion.select() must be called before wxPython is imported") 
 143     # Look for a matching version and manipulate the sys.path as 
 144     # needed to allow it to be imported. 
 145     installed 
= _find_installed(True) 
 146     bestMatch 
= _get_best_match(installed
, versions
, optionsRequired
) 
 148     if bestMatch 
is None: 
 149         raise VersionError("Requested version of wxPython not found") 
 151     sys
.path
.insert(0, bestMatch
.pathname
) 
 152     _selected 
= bestMatch
 
 154 #---------------------------------------------------------------------- 
 156 UPDATE_URL 
= "http://wxPython.org/" 
 157 #UPDATE_URL = "http://sourceforge.net/project/showfiles.php?group_id=10718" 
 161 def ensureMinimal(minVersion
, optionsRequired
=False): 
 163     Checks to see if the default version of wxPython is greater-than 
 164     or equal to `minVersion`.  If not then it will try to find an 
 165     installed version that is >= minVersion.  If none are available 
 166     then a message is displayed that will inform the user and will 
 167     offer to open their web browser to the wxPython downloads page, 
 168     and will then exit the application. 
 170     assert type(minVersion
) == str 
 172     # ensure that wxPython hasn't been imported yet. 
 173     if sys
.modules
.has_key('wx') or sys
.modules
.has_key('wxPython'): 
 174         raise VersionError("wxversion.ensureMinimal() must be called before wxPython is imported") 
 177     minv 
= _wxPackageInfo(minVersion
) 
 179     # check the default version first 
 180     defaultPath 
= _find_default() 
 182         defv 
= _wxPackageInfo(defaultPath
, True) 
 183         if defv 
>= minv 
and minv
.CheckOptions(defv
, optionsRequired
): 
 186     # if still no match then check look at all installed versions 
 187     if bestMatch 
is None: 
 188         installed 
= _find_installed() 
 189         # The list is in reverse sorted order, so find the first 
 190         # one that is big enough and optionally matches the 
 192         for inst 
in installed
: 
 193             if inst 
>= minv 
and minv
.CheckOptions(inst
, optionsRequired
): 
 197     # if still no match then prompt the user 
 198     if bestMatch 
is None: 
 199         if _EM_DEBUG
: # We'll do it this way just for the test code below 
 200             raise VersionError("Requested version of wxPython not found") 
 202         import wx
, webbrowser
 
 203         versions 
= "\n".join(["      "+ver 
for ver 
in getInstalled()]) 
 204         app 
= wx
.PySimpleApp() 
 205         result 
= wx
.MessageBox("This application requires a version of wxPython " 
 206                                "greater than or equal to %s, but a matching version " 
 208                                "You currently have these version(s) installed:\n%s\n\n" 
 209                                "Would you like to download a new version of wxPython?\n" 
 210                                % (minVersion
, versions
), 
 211                       "wxPython Upgrade Needed", style
=wx
.YES_NO
) 
 213             webbrowser
.open(UPDATE_URL
) 
 217     sys
.path
.insert(0, bestMatch
.pathname
) 
 219     _selected 
= bestMatch
 
 222 #---------------------------------------------------------------------- 
 224 def checkInstalled(versions
, optionsRequired
=False): 
 226     Check if there is a version of wxPython installed that matches one 
 227     of the versions given.  Returns True if so, False if not.  This 
 228     can be used to determine if calling `select` will succeed or not. 
 230         :param versions: Same as in `select`, either a string or a list 
 231             of strings specifying the version(s) to check for. 
 233         :param optionsRequired: Same as in `select`. 
 236     if type(versions
) == str: 
 237         versions 
= [versions
] 
 238     installed 
= _find_installed() 
 239     bestMatch 
= _get_best_match(installed
, versions
, optionsRequired
) 
 240     return bestMatch 
is not None 
 242 #---------------------------------------------------------------------- 
 246     Returns a list of strings representing the installed wxPython 
 247     versions that are found on the system. 
 249     installed 
= _find_installed() 
 250     return [os
.path
.basename(p
.pathname
)[3:] for p 
in installed
] 
 254 #---------------------------------------------------------------------- 
 257 def _get_best_match(installed
, versions
, optionsRequired
): 
 260     for pkg 
in installed
: 
 262             score 
= pkg
.Score(_wxPackageInfo(ver
), optionsRequired
) 
 263             if score 
> bestScore
: 
 269 _pattern 
= "wx-[0-9].*" 
 270 def _find_installed(removeExisting
=False): 
 275         # empty means to look in the current dir 
 279         # skip it if it's not a package dir 
 280         if not os
.path
.isdir(pth
): 
 283         base 
= os
.path
.basename(pth
) 
 285         # if it's a wx path that's already in the sys.path then mark 
 286         # it for removal and then skip it 
 287         if fnmatch
.fnmatchcase(base
, _pattern
): 
 291         # now look in the dir for matching subdirs 
 292         for name 
in glob
.glob(os
.path
.join(pth
, _pattern
)): 
 293             # make sure it's a directory 
 294             if not os
.path
.isdir(name
): 
 296             # and has a wx subdir 
 297             if not os
.path
.exists(os
.path
.join(name
, 'wx')): 
 299             installed
.append(_wxPackageInfo(name
, True)) 
 303             del sys
.path
[sys
.path
.index(rem
)] 
 310 # Scan the sys.path looking for either a directory matching _pattern, 
 314         # empty means to look in the current dir 
 318         # skip it if it's not a package dir 
 319         if not os
.path
.isdir(pth
): 
 322         # does it match the pattern? 
 323         base 
= os
.path
.basename(pth
) 
 324         if fnmatch
.fnmatchcase(base
, _pattern
): 
 330         if not os
.path
.isdir(pth
): 
 332         if os
.path
.exists(os
.path
.join(pth
, 'wx.pth')): 
 333             base 
= open(os
.path
.join(pth
, 'wx.pth')).read() 
 334             return os
.path
.join(pth
, base
) 
 339 class _wxPackageInfo(object): 
 340     def __init__(self
, pathname
, stripFirst
=False): 
 341         self
.pathname 
= pathname
 
 342         base 
= os
.path
.basename(pathname
) 
 343         segments 
= base
.split('-') 
 345             segments 
= segments
[1:] 
 346         self
.version 
= tuple([int(x
) for x 
in segments
[0].split('.')]) 
 347         self
.options 
= segments
[1:] 
 350     def Score(self
, other
, optionsRequired
): 
 353         # whatever number of version components given in other must 
 355         minlen 
= min(len(self
.version
), len(other
.version
)) 
 356         if self
.version
[:minlen
] != other
.version
[:minlen
]: 
 360         # check for matching options, if optionsRequired then the 
 361         # options are not optional ;-) 
 362         for opt 
in other
.options
: 
 363             if opt 
in self
.options
: 
 365             elif optionsRequired
: 
 371     def CheckOptions(self
, other
, optionsRequired
): 
 372         # if options are not required then this always succeeds 
 373         if not optionsRequired
: 
 375         # otherwise, if we have any option not present in other, then 
 377         for opt 
in self
.options
: 
 378             if opt 
not in other
.options
: 
 384     def __lt__(self
, other
): 
 385         return self
.version 
< other
.version 
or \
 
 386                (self
.version 
== other
.version 
and self
.options 
< other
.options
) 
 387     def __le__(self
, other
): 
 388         return self
.version 
<= other
.version 
or \
 
 389                (self
.version 
== other
.version 
and self
.options 
<= other
.options
) 
 391     def __gt__(self
, other
): 
 392         return self
.version 
> other
.version 
or \
 
 393                (self
.version 
== other
.version 
and self
.options 
> other
.options
) 
 394     def __ge__(self
, other
): 
 395         return self
.version 
>= other
.version 
or \
 
 396                (self
.version 
== other
.version 
and self
.options 
>= other
.options
) 
 398     def __eq__(self
, other
): 
 399         return self
.version 
== other
.version 
and self
.options 
== other
.options
 
 403 #---------------------------------------------------------------------- 
 405 if __name__ 
== '__main__': 
 408     #ensureMinimal('2.5') 
 409     #pprint.pprint(sys.path) 
 413     def test(version
, optionsRequired
=False): 
 415         savepath 
= sys
.path
[:] 
 418         select(version
, optionsRequired
) 
 419         print "Asked for %s, (%s):\t got: %s" % (version
, optionsRequired
, sys
.path
[0]) 
 422         sys
.path 
= savepath
[:] 
 427     def testEM(version
, optionsRequired
=False): 
 429         savepath 
= sys
.path
[:] 
 432         ensureMinimal(version
, optionsRequired
) 
 433         print "EM: Asked for %s, (%s):\t got: %s" % (version
, optionsRequired
, sys
.path
[0]) 
 436         sys
.path 
= savepath
[:] 
 441     # make some test dirs 
 442     names 
= ['wx-2.4-gtk-ansi', 
 443              'wx-2.5.2-gtk2-unicode', 
 445              'wx-2.6-gtk2-unicode', 
 448              'wx-2.7.1-gtk2-ansi', 
 451         d 
= os
.path
.join('/tmp', name
) 
 453         os
.mkdir(os
.path
.join(d
, 'wx')) 
 455     # setup sys.path to see those dirs 
 456     sys
.path
.append('/tmp') 
 460     pprint
.pprint( getInstalled()) 
 461     print checkInstalled("2.4") 
 462     print checkInstalled("2.5-unicode") 
 463     print checkInstalled("2.99-bogus") 
 464     print "Current sys.path:" 
 465     pprint
.pprint(sys
.path
) 
 476     test(["2.6-unicode", "2.7-unicode"]) 
 478     test(["2.6-unicode", "2.7-unicode"], optionsRequired
=True) 
 482     # There isn't a unicode match for this one, but it will give the best 
 483     # available 2.4.  Should it give an error instead?  I don't think so... 
 486     # Try asking for multiple versions 
 487     test(["2.5.2", "2.5.3", "2.6"]) 
 490         # expecting an error on this one 
 492     except VersionError
, e
: 
 493         print "Asked for 2.9:\t got Exception:", e 
 
 495     # check for exception when incompatible versions are requested 
 499     except VersionError
, e
: 
 500         print "Asked for incompatible versions, got Exception:", e 
 
 504     testEM("2.6-unicode") 
 505     testEM("2.6-unicode", True) 
 508     except VersionError
, e
: 
 509         print "EM: Asked for 2.9:\t got Exception:", e 
 
 513         d 
= os
.path
.join('/tmp', name
) 
 514         os
.rmdir(os
.path
.join(d
, 'wx'))