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

Darren Dale dsdale24@gmail....
Wed Jul 29 14:21:46 CDT 2009


Hi Brian,

On Wed, Jul 29, 2009 at 2:17 PM, Brian Granger<ellisonbg.net@gmail.com> wrote:
> 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.

This seems a bit awkward to me. Why not do:

c = MyComponent(..., config=(config1,config2,...))

and have the component complete any remaining init related things at
the end of __init__?

> 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.

I guess I don't understand the motivation for this extra degree of
separation, why are run-time attributes held by an objects rc struct
instead of by the object itself? It seems more natural to use
properties.

> 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.

I think you could maybe do all of this with properties. Notifications
could be handled with a decorator.

> 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.

I think this is related to my comments above.

> 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?

I agree. It would be confusing trying to figure out whether the
desired attribute or method is found on the component itself or on the
rc attr. Maybe I'm just not familiar with any projects that do it this
way, so the is-a paradigm feels more natural.

Darren


More information about the IPython-dev mailing list