[Tkinter-discuss] Adding something in Tkinter to facilitate package loading

Guilherme Polo ggpolo at gmail.com
Mon Feb 2 05:21:31 CET 2009


Hi there,

I was considering the possibility of adding something to facilitate
package loading in Tkinter. If you have done a wrapper, or used one,
you may have noticed that there is always some code like this:

        global _TKTABLE_LOADED
        if not _TKTABLE_LOADED:
            tktable_lib = os.environ.get('TKTABLE_LIBRARY')
            if tktable_lib:
                master.tk.eval('global auto_path; '
                                'lappend auto_path {%s}' % tktable_lib)
            master.tk.call('package', 'require', 'Tktable')
            _TKTABLE_LOADED = True

This one was taken from the tktable wrapper (as you can see), some
details will change from wrapper to wrapper (but apparently not by
much).

There is also the possibility that the wrapper modifies Tkinter.Tk
(see ttk), or subclass it (see Tix), to inject this kind of code --
which may be problematic. I see it as problematic when you want to
test, lets say, a module P that depends on X and modifies Tkinter.Tk,
and another module Q that depends on Y (but no X). When P is executed
it does whatever it needs to load X, if loading X succeeds then it is
all good, but lets suppose it failed. Now Q executes and tries
instantiating Tkinter.Tk, the problem is that it doesn't depend on X
to execute but P already modified it to load X (but it failed), now Q
fails too. Maybe I'm just bringing this up because I decided to modify
Tkinter.Tk on ttk, expected some form of problems initially, but
started having real problems just now.

Anyway, even if the second reason looks bad, I still would like to
have something to load packages easily. I was thinking something like:


tcl_deps = {}
tk_deps = {}

def add_pkgdep(ident, pkg_loader, depends_on='tk'):
    """Add a new package to be loaded after the interpreter is created.

    ident is just any identifier (that can be used as a dict key), to
    identify your pkg_loader. If another pkg_loader is given with the same
    ident then a DuplicatePkgIdentError is raised.

    pkg_loader should be a callable which takes a single argument. It will
    be called with a Tk instance.

    If depends_on is set to 'tk', then the package loader will be invoked
    after tk loads and only if the interpreter loads tk.
    """
    if depends_on == 'tk':
        d = tk_deps
    else:
        d = tcl_deps

    if ident in tk_deps or ident in tcl_deps:
        raise DuplicatePkgIdentError("%r is already taken" % ident)
    d[ident] = pkg_loader

def pop_pkgdep(ident):
    """Remove and return the package loader associated with ident from the
    dependencies."""
    if ident in tk_deps:
        return tk_deps.pop(ident)
    return tcl_deps.pop(ident)

def clear_pkgdeps():
    """Clear all dependencies currently set."""
    for item in (tcl_deps, tk_deps):
        item.clear()

def basic_dep_loader(master, pkg_name, env_var=None):
    dep_path = os.environ.get(env_var)
    if dep_path:
        # append custom dependence path to the the list of directories that
        # Tcl uses when attempting to resolve packages with the package
        # command
        master.tk.eval(
                'global auto_path; '
                'lappend auto_path {%s}' % dep_path)
    # TclError may be raised now
    master.tk.eval('package require %s' % pkg_name)


Some very small modifications (two lines actually) would be needed in
Tkinter.Tk to invoke the loaders at the right time, and wouldn't
affect the current usage of Tkinter.
Applying it in ttk, for example, would reduce this code (comments stripped):

def _loadttk(loadtk):
    def _wrapper(self):
        loadtk(self)

        if _REQUIRE_TILE:
            import os
            tilelib = os.environ.get('TILE_LIBRARY')
            if tilelib:
                self.tk.eval('global auto_path; '
                             'lappend auto_path {%s}' % tilelib)
            self.tk.eval('package require tile') # TclError may be raised here

    return _wrapper

__loadtk__ = Tkinter.Tk._loadtk
Tkinter.Tk._loadtk = _loadttk(Tkinter.Tk._loadtk)


To this (besides making it easier to test):

def _tile_loader(master):
    # Load tile if needed, this will happen right after Tk loads.
    if _REQUIRE_TILE:
        Tkinter.basic_dep_loader(master, 'tile', 'TILE_LIBRARY')

Tkinter.add_pkgdep('tile', _tile_loader)


What do you think about this addition ? Should the order of packages
matter ? Add some introspection ?

-- 
-- Guilherme H. Polo Goncalves


More information about the Tkinter-discuss mailing list