]>
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 re
, 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
)
153 path64
= re
.sub('/lib/','/lib64/',bestMatch
.pathname
)
154 if os
.path
.isdir(path64
):
155 sys
.path
.insert(0, path64
)
156 _selected
= bestMatch
158 #----------------------------------------------------------------------
160 UPDATE_URL
= "http://wxPython.org/"
161 #UPDATE_URL = "http://sourceforge.net/project/showfiles.php?group_id=10718"
165 def ensureMinimal(minVersion
, optionsRequired
=False):
167 Checks to see if the default version of wxPython is greater-than
168 or equal to `minVersion`. If not then it will try to find an
169 installed version that is >= minVersion. If none are available
170 then a message is displayed that will inform the user and will
171 offer to open their web browser to the wxPython downloads page,
172 and will then exit the application.
174 assert type(minVersion
) == str
176 # ensure that wxPython hasn't been imported yet.
177 if sys
.modules
.has_key('wx') or sys
.modules
.has_key('wxPython'):
178 raise VersionError("wxversion.ensureMinimal() must be called before wxPython is imported")
181 minv
= _wxPackageInfo(minVersion
)
183 # check the default version first
184 defaultPath
= _find_default()
186 defv
= _wxPackageInfo(defaultPath
, True)
187 if defv
>= minv
and minv
.CheckOptions(defv
, optionsRequired
):
190 # if still no match then check look at all installed versions
191 if bestMatch
is None:
192 installed
= _find_installed()
193 # The list is in reverse sorted order, so find the first
194 # one that is big enough and optionally matches the
196 for inst
in installed
:
197 if inst
>= minv
and minv
.CheckOptions(inst
, optionsRequired
):
201 # if still no match then prompt the user
202 if bestMatch
is None:
203 if _EM_DEBUG
: # We'll do it this way just for the test code below
204 raise VersionError("Requested version of wxPython not found")
206 import wx
, webbrowser
207 versions
= "\n".join([" "+ver
for ver
in getInstalled()])
208 app
= wx
.PySimpleApp()
209 result
= wx
.MessageBox("This application requires a version of wxPython "
210 "greater than or equal to %s, but a matching version "
212 "You currently have these version(s) installed:\n%s\n\n"
213 "Would you like to download a new version of wxPython?\n"
214 % (minVersion
, versions
),
215 "wxPython Upgrade Needed", style
=wx
.YES_NO
)
217 webbrowser
.open(UPDATE_URL
)
221 sys
.path
.insert(0, bestMatch
.pathname
)
223 path64
= re
.sub('/lib/','/lib64/',bestMatch
.pathname
)
224 if os
.path
.isdir(path64
):
225 sys
.path
.insert(0, path64
)
227 _selected
= bestMatch
230 #----------------------------------------------------------------------
232 def checkInstalled(versions
, optionsRequired
=False):
234 Check if there is a version of wxPython installed that matches one
235 of the versions given. Returns True if so, False if not. This
236 can be used to determine if calling `select` will succeed or not.
238 :param versions: Same as in `select`, either a string or a list
239 of strings specifying the version(s) to check for.
241 :param optionsRequired: Same as in `select`.
244 if type(versions
) == str:
245 versions
= [versions
]
246 installed
= _find_installed()
247 bestMatch
= _get_best_match(installed
, versions
, optionsRequired
)
248 return bestMatch
is not None
250 #----------------------------------------------------------------------
254 Returns a list of strings representing the installed wxPython
255 versions that are found on the system.
257 installed
= _find_installed()
258 return [os
.path
.basename(p
.pathname
)[3:] for p
in installed
]
262 #----------------------------------------------------------------------
265 def _get_best_match(installed
, versions
, optionsRequired
):
268 for pkg
in installed
:
270 score
= pkg
.Score(_wxPackageInfo(ver
), optionsRequired
)
271 if score
> bestScore
:
277 _pattern
= "wx-[0-9].*"
278 def _find_installed(removeExisting
=False):
283 # empty means to look in the current dir
287 # skip it if it's not a package dir
288 if not os
.path
.isdir(pth
):
291 base
= os
.path
.basename(pth
)
293 # if it's a wx path that's already in the sys.path then mark
294 # it for removal and then skip it
295 if fnmatch
.fnmatchcase(base
, _pattern
):
299 # now look in the dir for matching subdirs
300 for name
in glob
.glob(os
.path
.join(pth
, _pattern
)):
301 # make sure it's a directory
302 if not os
.path
.isdir(name
):
304 # and has a wx subdir
305 if not os
.path
.exists(os
.path
.join(name
, 'wx')):
307 installed
.append(_wxPackageInfo(name
, True))
311 del sys
.path
[sys
.path
.index(rem
)]
318 # Scan the sys.path looking for either a directory matching _pattern,
322 # empty means to look in the current dir
326 # skip it if it's not a package dir
327 if not os
.path
.isdir(pth
):
330 # does it match the pattern?
331 base
= os
.path
.basename(pth
)
332 if fnmatch
.fnmatchcase(base
, _pattern
):
338 if not os
.path
.isdir(pth
):
340 if os
.path
.exists(os
.path
.join(pth
, 'wx.pth')):
341 base
= open(os
.path
.join(pth
, 'wx.pth')).read()
342 return os
.path
.join(pth
, base
)
347 class _wxPackageInfo(object):
348 def __init__(self
, pathname
, stripFirst
=False):
349 self
.pathname
= pathname
350 base
= os
.path
.basename(pathname
)
351 segments
= base
.split('-')
353 segments
= segments
[1:]
354 self
.version
= tuple([int(x
) for x
in segments
[0].split('.')])
355 self
.options
= segments
[1:]
358 def Score(self
, other
, optionsRequired
):
361 # whatever number of version components given in other must
363 minlen
= min(len(self
.version
), len(other
.version
))
364 if self
.version
[:minlen
] != other
.version
[:minlen
]:
368 # check for matching options, if optionsRequired then the
369 # options are not optional ;-)
370 for opt
in other
.options
:
371 if opt
in self
.options
:
373 elif optionsRequired
:
379 def CheckOptions(self
, other
, optionsRequired
):
380 # if options are not required then this always succeeds
381 if not optionsRequired
:
383 # otherwise, if we have any option not present in other, then
385 for opt
in self
.options
:
386 if opt
not in other
.options
:
392 def __lt__(self
, other
):
393 return self
.version
< other
.version
or \
394 (self
.version
== other
.version
and self
.options
< other
.options
)
395 def __le__(self
, other
):
396 return self
.version
<= other
.version
or \
397 (self
.version
== other
.version
and self
.options
<= other
.options
)
399 def __gt__(self
, other
):
400 return self
.version
> other
.version
or \
401 (self
.version
== other
.version
and self
.options
> other
.options
)
402 def __ge__(self
, other
):
403 return self
.version
>= other
.version
or \
404 (self
.version
== other
.version
and self
.options
>= other
.options
)
406 def __eq__(self
, other
):
407 return self
.version
== other
.version
and self
.options
== other
.options
411 #----------------------------------------------------------------------
413 if __name__
== '__main__':
416 #ensureMinimal('2.5')
417 #pprint.pprint(sys.path)
421 def test(version
, optionsRequired
=False):
423 savepath
= sys
.path
[:]
426 select(version
, optionsRequired
)
427 print "Asked for %s, (%s):\t got: %s" % (version
, optionsRequired
, sys
.path
[0])
430 sys
.path
= savepath
[:]
435 def testEM(version
, optionsRequired
=False):
437 savepath
= sys
.path
[:]
440 ensureMinimal(version
, optionsRequired
)
441 print "EM: Asked for %s, (%s):\t got: %s" % (version
, optionsRequired
, sys
.path
[0])
444 sys
.path
= savepath
[:]
449 # make some test dirs
450 names
= ['wx-2.4-gtk-ansi',
451 'wx-2.5.2-gtk2-unicode',
453 'wx-2.6-gtk2-unicode',
456 'wx-2.7.1-gtk2-ansi',
459 d
= os
.path
.join('/tmp', name
)
461 os
.mkdir(os
.path
.join(d
, 'wx'))
463 # setup sys.path to see those dirs
464 sys
.path
.append('/tmp')
468 pprint
.pprint( getInstalled())
469 print checkInstalled("2.4")
470 print checkInstalled("2.5-unicode")
471 print checkInstalled("2.99-bogus")
472 print "Current sys.path:"
473 pprint
.pprint(sys
.path
)
484 test(["2.6-unicode", "2.7-unicode"])
486 test(["2.6-unicode", "2.7-unicode"], optionsRequired
=True)
490 # There isn't a unicode match for this one, but it will give the best
491 # available 2.4. Should it give an error instead? I don't think so...
494 # Try asking for multiple versions
495 test(["2.5.2", "2.5.3", "2.6"])
498 # expecting an error on this one
500 except VersionError
, e
:
501 print "Asked for 2.9:\t got Exception:", e
503 # check for exception when incompatible versions are requested
507 except VersionError
, e
:
508 print "Asked for incompatible versions, got Exception:", e
512 testEM("2.6-unicode")
513 testEM("2.6-unicode", True)
516 except VersionError
, e
:
517 print "EM: Asked for 2.9:\t got Exception:", e
521 d
= os
.path
.join('/tmp', name
)
522 os
.rmdir(os
.path
.join(d
, 'wx'))