On 7/18/07, Brian Granger <[EMAIL PROTECTED]> wrote:

> > 3. Traits.  We (Brian and I) have gone back and forth a lot on Traits,
> > and we've come very close to just making them a dependency.  The only
> > real issue holding us back is that ipython so far has exactly *zero*
> > extension code, which is a plus in terms of ease of
> > installation/deployment.  Having said that, as far as extension code
> > is concerned, Traits is light and clean, and nowhere near the kinds of
> > complexities that MPL deals with.  But since anything is more than
> > zero, it is a bit of an issue for us.  We may tip over though, I'm
> > just stating what our reasoning so far has been.
> >
> > In terms of Traits, point (2) above makes them even more attractive.
> > The delegation aspect of Traits is a very appealing way of combining
> > validation with additional action for the runtime modification of an
> > object.  For example:
> >
> > ipython.color_scheme = "foo"
> >
> > If color_scheme were a Trait, the above could simply:
> >
> > a) validate that "foo" was acceptable as a value
> > b) trigger the chain of other trait updates (dependent color schemes
> > for exceptions, prompts, etc).
>
> At some level though, configuration is a very different thing than an
> application's runtime API.  While they may be related (by exposing
> common functionality), not everything that can be configured would
> appear in a runtime API and vice-versa.  Also, some events that need
> to happen when an attribute is changed at runtime can't happen at
> config time as the application might not yet be up and running yet.

[ Brian, I know we already talked about this, but I'm putting the full
reasoning here because I'm interested in this discussion going beyond
ipython to mpl and possibly other tools in this little 'universe' of
ours]

I should have provided more detail in my previous email, to address
your (valid) concerns.  Here's how I view organizing things: objects
have:

1. an init-time-only configuration part, which after construction is read-only,

2. a part which can be set at init or runtime

3. an api for purely run-time behavior, that can't be exposed at init
time because it fundamentally needs the object to exist.  This is the
'user public' api of the object instead of the 'developer public' one
(in the sense of not having _underscore prefixes).

1 and 2 can be expressed declaratively and the .conf format that
ConfigObj reads is very nice for that.  3 can only exist at runtime,
so it's a matter of having a way in the .conf file for the user to
declare a .py file that will be run AFTER object construction.

> Using Traits in the runtime API is an entirely different ballgame than
> simply using it for type validation in configuration:  When using
> Traits for validation+configuration, the objects that inherit from
> HasTraits are simply bunch/dict like objects that do type validation -
> but they don't contain any application logic.  The actually
> application logic is contained in classes that aren't traited
> themselves, but that consume traited config objects to configure
> themselves at startup.
>
> If traited objects are exposed in a runtime API, all of a sudden the
> application logic moves to the traited classes themselves.  Then, the
> entire application (any object that needs config or has a runtime API)
> is built upon traits at its core.  This is very from the previous case
> where traited classes are used as a minor implementation detail of the
> config system.
>
> I am not saying that it is bad to build an application with traits at
> its core, but only that that is very different from the path that
> ipython and matplotlib have taken thus far.  Also, it makes the
> commitment level to traits much higher than if it is used merely as a
> component in the config system - which could easily be swapped out if
> desired.

As I said elsewhere, Traits is *really* small and self-contained, with
a single super-clean piece of C-only extension code that compiles
quickly and without a single warning.  I'm still waiting for the
Enthought guys to help me with the metaclass hacks to fully disable
the GUI support at will, but I'm sure it's doable (I even already have
a solution, I just want a better one).

I'm honestly 90% convinced of just using Traits now.  The reason is here:

svn co http://ipython.scipy.org/svn/ipython/ipython/branches/saw/sandbox

Just run the file tconfig.py there from within ipython, and start
playing for example with the mpl.rc object.  There are a few things I
dislike:

1. Any traited object has a very dirty namespace out of the box.  For
rc objects, this makes tab-completion a royal pain in the ass.  We'll
see what the Enthought gurus suggest on this front.

2. The way I'm handling read-only name displays in the string
representation of these things is a bit ugly.  But the mix of
declarative use of classes, multiple inheritance and traits'
metaclasses got me a bit confused and I couldn't find a cleaner
solution.


But I think this approach can be very nice.  For those not interested
in downloading the code, the highlights are:

- Consider a file called mpl.conf:
# Top-level
backend = "TkAgg"
interactive = False

# Things that can only be set at init time, they become read-only afterwards
[InitOnly]
numerix = "numpy"

# Other sections
[lines]
linewidth = 2.0
linestyle = '-'

[figure]
figsize   = [6.4,4.8]  # figure size in inches
dpi       = 100        # figure dots per inch
facecolor = 0.75       # figure facecolor; 0.75 is scalar gray
edgecolor = "white"    # figure edgecolor

    [[subplot]]
    # The figure subplot parameters.  All dimensions are fraction of the
    # figure width or height
    left  = 0.125  # the left side of the subplots of the figure
    right = 0.9    # the right side of the subplots of the figure
    bottom = 0.1   # the bottom of the subplots of the figure
    top = 0.9      # the top of the subplots of the figure

This can be edited in any text editor, emacs even has a .conf major mode!

- Then, consider the following bit of Python code:

    standard_color = Trait ('black',
                            {'black': (0.0, 0.0, 0.0, 1.0),
                             'blue': (0.0, 0.0, 1.0, 1.0),
                             'cyan': (0.0, 1.0, 1.0, 1.0),
                             'green': (0.0, 1.0, 0.0, 1.0),
                             'magenta': (1.0, 0.0, 1.0, 1.0),
                             'orange': (0.8, 0.196, 0.196, 1.0),
                             'purple': (0.69, 0.0, 1.0, 1.0),
                             'red': (1.0, 0.0, 0.0, 1.0),
                             'violet': (0.31, 0.184, 0.31, 1.0),
                             'yellow': (1.0, 1.0, 0.0, 1.0),
                             'white': (1.0, 1.0, 1.0, 1.0),
                             'transparent': (1.0, 1.0, 1.0, 0.0) } )

    class MPLConfig(TConfig):

        # Valid backends, first is default
        backend = Trait('TkAgg','WXAgg','GTKAgg','QtAgg','Qt4Agg')
        interactive = Bool(False)

        class InitOnly(TConfig,ReadOnlyTConfig):
            """Things that can only be set at init time"""
            numerix = Str('numpy')

        class lines(TConfig):
            linewidth = Float(2.0)
            linestyle = Trait('-','=','^')

        class figure(TConfig):
            figsize = ListFloat([6.4,4.8])  # figure size in inches
            dpi = Int(100)            # figure dots per inch
            facecolor = Float(0.75)    # figure facecolor; 0.75 is scalar gray
            edgecolor = Trait('white',standard_color)

            class subplot(TConfig):
                """The figure subplot parameters.  All dimensions are fraction
                of the figure width or height"""
                left = Float(0.125)
                right = Float(0.9)
                bottom = Float(0.1)
                top = Float(0.9)

This purely declarative code incorporates:

 * The types of all parameters acceptable in the file
 * The nesting hierarchy for subobjects
 * The default values hardcoded in the app if users don't provide any
 * The valid values for those which are choices.

An 'application' is defined via:

    class App(object):
        """A trivial 'application' class to be initialized.
        """
        def __init__(self,configClass,conf_filename):
            conf = mkConfigObj(conf_filename)
            self.rc = configClass(conf)


    mpl = App(MPLConfig,'mpl.conf')

This makes the App object load the .conf file, immediately giving
errors if any of the validation constraints implicit in the traits
spec are not met.  The resulting mpl.rc object can be printed,
manipulated, modified (for non-read-only sections), even edited with a
GUI (limited at the moment to WX and not really working for
sub-sections, though that's easily fixed).  For example:

In [159]: mpl.rc
Out[159]:
# Dump of MPLConfig

backend = 'TkAgg'
interactive = False

[InitOnly]
numerix = 'numpy'

[lines]
linewidth = 2.0
linestyle = '-'

[figure]
edgecolor = 'white'
figsize = [6.4000000000000004, 4.7999999999999998]
dpi = 100
facecolor = 0.75

    [[subplot]]
    top = 0.90000000000000002
    right = 0.90000000000000002
    left = 0.125
    bottom = 0.10000000000000001

These objects are automatically self-representable in a valid
roundtrip format.  Editing works:

In [160]: mpl.rc.interactive
Out[160]: False

In [161]: mpl.rc.interactive = True

with validation:

In [162]: mpl.rc.interactive = "well"
---------------------------------------------------------------------------
TraitError                                Traceback (most recent call last)

/home/fperez/ipython/svn/ipython/branches/saw/sandbox/<ipython
console> in <module>()

/home/fperez/usr/opt/lib/python2.5/site-packages/enthought/traits/trait_handlers.py
in error(self, object, name, value)
    169         another trait handler to handle to validate the value.
    170         """
--> 171         raise TraitError, ( object, name, self.info(), value )
    172
    173     def arg_error ( self, method, arg_num, object, name, value ):

TraitError: The 'interactive' trait of a MPLConfig instance must be a
value of type 'bool', but a value of well was specified.


And read-only parts can't be modified:

In [163]: mpl.rc.InitOnly.numerix = "Numeric"
---------------------------------------------------------------------------
TraitError                                Traceback (most recent call last)

/home/fperez/ipython/svn/ipython/branches/saw/sandbox/<ipython
console> in <module>()

TraitError: Cannot modify the read only 'numerix' attribute of a
'ReadOnlyTConfig' object.



In summary, I'm fairly happy with the results, and I think the benefit
is enough to convince me of falling in the embrace of the gods of
Traits.  It seems John is going for Traits as well, so perhaps we can
use this little config setup across our systems, and even make it
something that others use in the future.  I think there's value for
end users in having common, uniform configuration systems across the
various parts of the scientific python 'ecosystem'.

Once we have a discussion here, I'll take this over to the ipython list as well.

Cheers,

f

-------------------------------------------------------------------------
This SF.net email is sponsored by: Microsoft
Defy all challenges. Microsoft(R) Visual Studio 2005.
http://clk.atdmt.com/MRT/go/vse0120000070mrt/direct/01/
_______________________________________________
Matplotlib-devel mailing list
Matplotlib-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/matplotlib-devel

Reply via email to