[IPython-dev] App's, Components and refactoring

Brian Granger ellisonbg.net at gmail.com
Wed Jul 29 14:17:36 EDT 2009


Hi,

I have been banging my head on the IPython refactoring this week.  Ouch.  I
am specifically thinking about the config system, IP.rc and ipmaker stuff.

Yesterday, Fernando and I talked about some of my ideas and we tentatively
settled on some abstractions that I think will greatly help in the
refactoring.  I wanted to sketch these out to get feedback from folks.

Here are the main abstractions:

1.  Config objects.  Bags of configuration variables that can be read from
file or the command line.
2.  Component.  An object that be configured and has a runtime api.  Just
about everything in IPython will be a component.
1.  App - a top-level IPython program like ipython, ipythonx, ipcluster,
ipengine, ipcontroller.

The job of an App will be to assemble and merge all relevant config objects
and then instantiate and configure components.  How do components get
created and configured?

c = MyComponent()  # this build it with class defaults
c.configure(config) # The App or parent component passes the config to the
object to the component to override the defaults
c.freeze_config() # This tells the component that config is done and that it
should complete any remaining init related things.

>From then on, the component ignore the config info and instead relies on its
runtime data.  Each component will have a Struct-like object that contains
the runtime data named rc = 'runtime configuration'.  Notice, this is
extremely similar to the existing design on IP.  BUT, it is very different
because each component will hold its own runtime attributes (IP won't hold
many at all).  The golden rule of the runtime attributes is this: if you can
change them, IPython's behavior *must* change appropriately.

Components will have some sort of registry and querying system that will
allow different parts of IPython to get the runtime api for other
components.  This will also be completely independent of IP itself.  It will
look something like this:

rc = Component.find_component_runtime('prompt1', Prompt) # get the runtime
api for component named 'prompt1' that is an instance of Prompt

Once you have one of these runtime objects you can do a number of things:

* read its attributes (rc.prompt_string)
* write its attributes
* setup notifications rc.add_notification('prompt_string', callable) #
callable is called if prompt_string changes.

If you are thinking these runtime objects look a lot like enthought.traits,
you are right.  While we can't use traits, the design of these objects will
be very much traits inspired.

It will be up to each component to pass the config object it gets onto its
children (by calling their configure methods).  Thus the config objects will
propagate down the component graph as it is created.

There are still many things to work out.  After sleeping on it, I have some
questions...

In many cases, components will have not only attributes that can be set/get
by others, but also methods.  Where to we put these methods?  It makes the
most sense to put them on the Components themselves.  This makes me think
that the component/runtime-api relationship should be a "is-a" not a
"has-a".  The "is-a" approach is more of what Traits uses.  Initially, our
instinct was to use "has-a", to keep the component namespace clean.  But,
now I am almost thinking that when other objects get a "runtime api" for a
component, what they want is the component itself.

Here is what the "is-a" looks like:

c = Component.find_component('shell', InteractiveShell)
c.autocall = False
c.runlines(...)

And here is the "has-a":

c = Component.find_component('shell', InteractiveShell)
c.rc.autocall = False
c.runlines(...)

Or we could put the runtime api entirely on the rc attribute:

rc = Component.find_runtime_api('shell',InteractiveShell)
rc.autocall = False
c.runlines(...)

Here is a short argument against having a separate runtime (I am arguing for
the "is-a" approach).  This (the 'has-a') is what we use with
InteractiveShell and ipapi.  ipapi is a thin wrapper around
InteractiveShell.  The problem with this is that you can end up going in
circles.  When do you call ipapi and when do you just call InteractiveShell
methods?  It gets darned confusing.  This confusion shows up in silly things
like methods of InteractiveShell using ipapi rather than just calling its
own methods!  I think this is an abuse of OO design.  Objects themselves
have public APIs.  You don't need the extra indirection of a public API that
wraps that public API.  I think the original reason we came up with ipapi is
that InteractiveShell didn't have a clean public api.  But, once that is
fixed, I think ipapi should go away.

Thoughts?  Feeback?

Cheers,

Brian
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.python.org/pipermail/ipython-dev/attachments/20090729/14a665d4/attachment.html>


More information about the IPython-dev mailing list