[Twisted-Python] Configuration: The Never Ending Story.

Chris Armstrong carmstro at dynup.net
Fri May 11 12:36:24 EDT 2001

[Ed.: I'm sending it on behalf of Chris since his mail server is down.
I have not edited the mail, and I will reply to it seperately]

Alright, I discussed this with moshe for a while on IRC, and here's my summary
of the discussion.

On Fri, May 11, 2001 at 04:53:15PM +0300, Moshe Zadka wrote:
> OK, let me first state some axioms:
> 1. Everything should be configurable the same way, from within twisted.
> Glyph has mentioned that he's not familiar with what's in bin/ anymore: 
> he just mktelnetserver, and configures from within the telnet server
> everything. This is what should happen, except not limited to the telnet
> server.

Yes, this is definitely good, and planned this way.

> 2. The configuration interface must not be ui specific.
> This is obvious, right? Command line and web based aren't the only option.
> No reason why we can't implement a special purpose client/server for configuring
> twisted.

Again, this was planned. web UIs, reality UIs (you enter the magic box, there
is a web server floating here. You configure the web server.), GTK UIs are
all distant goals.

> 3. It should be easy to optimize the configuration for a ui
> This is like the last one -- since we might deal with very good UIs, we
> need to give them enough information for using all their abilities to
> help us.
> 4. It should be very easy to make an object configurable.
> This is very important -- the harder it is, the less it will be easy
> to add *good* code to twisted. This is what may be the single most idiotic
> thing in Zope (and DC is aware of it! and thinking of how to fix it!). Let's
> learn from their mistakes: the less methods, the better. The more the
> learning curve is gradual (not needing to learn a class/method/interface
> before you need the functionality), the better.

I think this is also very important to end-user acceptance of TPy. If it's
got an easy-to-use and robust configurator, people will use it.

> OK, so what do we need to do about it?
> Here's a rough proposition:
> the configurable *interface*, which will be a class, but not a class
> people should inherit from, will contain the following methods:
> .getQuestions() --> return a dictionary: name of question, Question object

name change: getParamaters().

> .getAnswer(name, answer) --> notify the application that an answer has been
>                              given to a particular question
>                              It can throw an InvalidAnswer exception with
>                              a string for a reason. This is for 
>                              application-level verification, and is discouraged.

name change: setParamaters()
Also, throwing the exception is what's discouraged, not the actual use of the
method. :)

> .endAnswer() --> the "application" promises that no methods of the object
>                  will be called between a series of .getAnswer()s and 
>                  .endAnswer(). So, this means that if the UI got a bunch 
>                  of answers, it will call .getAnswer() several times, and
>                  then .endAnswer(). The UI will *check* for this method's
>                  existance, and will not call it if it doesn't exist.

name change: i'm not sure what, but endAnswer doesn't sound right.
What this is for is deferred calculation of answers. the not-calling-methods
thing needs to be thought about, so we don't have some weird bugs where TPy
goes completely haywire while configuring. (For instance, what if we're
configuring a web server through the web interface?)

> Question objects are meant to be open ended. 
> They can contain a default.
> Here is the general interface of the Question, that all objects conform
> too:
> .hasAnswer() --> boolean, whether the Question already contains an 
>                  answer/default
> .getValue() --> will only work if .hasAnswer() is true, returns the answer
> .setValue(val) --> make .hasAnswer() true

name change: hasValue()?

> Objects which can be created by the UI should have an __init__ which
> can be called without arguments. If there is any initialization which
> requires arguments, it should be done in endAnswer(). The UI also promises
> not to call .endAnswer() for an object if there any questions which have
> not been answered and do not have a default (.hasAnswer is false.)

Ok, instead of __init__s that don't take arguments, we have klass.getInitArgs()
that is another set of questions that the user answers before the UI
instantiates the object (well, it'll have to be klass.__dict__['getInitArgs'](),
as glyph pointed out to me). Also, when we instantiate the object, we call
newObject.setParent(parent), as moshe talks about in the open questions at
the bottom of his mail.

> Well, my proposal would not be complete if I didn't say what questions are
> available. Keep in mind, though, that the set of questions is *open ended*.
> That does not violate the light-and-lean guidelines, since a specific
> Question class will only be used if the functionality is needed.
> Without further ado:

name change for all of the following: *Parameter

> class BooleanQuestion:
> class IntQuestion: (can have .min and .max)
> class FloatQuestion:
> class StringQuestion: (can have .maxlength)
> class LongStringQuestion: (same as above -- it's a hint to the ui)
> class InterfaceQuestion: (specify interface, valid answers are objects)
> class ArrayQuestion: (an array of the same kind of question)

moshe told me this was a thinko, so forget about array of questions :)

> class DictQuestion: (a dictionary mapping strings -> same kind of question)

I don't really get this one.

> example:
> class Server: # note -- not inheriting from anything
>     name = port = None
>     def getQuestions(self):
>         name = StringQuestion()
>         if self.name is not None:
>             name.setValue(self.name)
>         port = IntQuestion()
>         if self.port is not None:
>             port.setValue(self.port)
>         return {'port': port, 'name': name}
>     def getAnswer(self, name, answer):
>         # note: no need to use int(answer)
>         # for port: an IntQuestion has an integer as a .getValue()
>         setattr(self, name, answer)
> Open questions:
> * How do we connect classes to interfaces?
>   Suggestion: each class has an attribute __implements__ containig a list
>   of interfaces. Alternatively, an interface is really a list of 
>   (callable, Questions) tuples, which are answered and passed to the callable.
>   Modules with relevant classes register with the correct interface.

I seem to remember some standardization of this that was proposed in a PEP.
Maybe we should use that standard for now, but of course just use our own
implementation of the implementation-checking stuff. (tongue twister, eh?)

> * How do we let objects created inside another object who their parent are?
>   Suggestion: if an object from on Interface question has a method .setParent,
>   call it with the parent as argument. This should be done by the Question
>   object, so it knows how to call it for each object in a ListQuestion

After thinking about this for a while, I decided it's a good idea. At first,
it seemed like an arbitrary fix to a problem, but when moshe brought up
how useful it would be when "moving" objects around.

> * How do we let objects "title" a question?
>   Suggestion: a question has a string argument on __init__ titling it.

that's simple enough.

> * How do we allow the object to signify groupings of questions?
>   Suggestion: this means the design is bad -- break down the object
>   into smaller objects

I'm not sure of my stance on this. I'll try to think of some examples and

Chris Armstrong                        carmstro at twistedmatrix.com
http://twistedmatrix.com/~carmstro     carmstro at dynup.net

More information about the Twisted-Python mailing list