[Twisted-Python] Configuration: The Never Ending Story.
![](https://secure.gravatar.com/avatar/faeb19fbb879e56c1be4a300cfd80ec8.jpg?s=120&d=mm&r=g)
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. 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. 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. 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 .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. .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. 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 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.) 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: 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) class DictQuestion: (a dictionary mapping strings -> same kind of question) 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. * 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 * How do we let objects "title" a question? Suggestion: a question has a string argument on __init__ titling it. * 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'll be ex-DPL soon anyway so I'm |LUKE: Is Perl better than Python? looking for someplace else to grab power."|YODA: No...no... no. Quicker, -- Wichert Akkerman (on debian-private)| easier, more seductive. For public key, finger moshez@debian.org |http://www.{python,debian,gnu}.org
![](https://secure.gravatar.com/avatar/daf660d6890426570e2e2188f4f7b27e.jpg?s=120&d=mm&r=g)
[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:
Yes, this is definitely good, and planned this way.
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.
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.
name change: getParamaters().
name change: setParamaters() Also, throwing the exception is what's discouraged, not the actual use of the method. :)
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?)
name change: hasValue()?
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.
name change for all of the following: *Parameter
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.
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?)
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.
I'm not sure of my stance on this. I'll try to think of some examples and discuss. -- Chris Armstrong carmstro@twistedmatrix.com http://twistedmatrix.com/~carmstro carmstro@dynup.net
![](https://secure.gravatar.com/avatar/faeb19fbb879e56c1be4a300cfd80ec8.jpg?s=120&d=mm&r=g)
On Fri, 11 May 2001 19:36:24 +0300, Chris Armstrong <carmstro@dynup.net> wrote: <snipped agreement on the axioms>
setParameter() sans the 's'. One call per parameter
That's the classical example for why it's needed. Since the UI should do something like that for parameter in stuff_i_got: object.setParameter(...) object.endParameters() Then everything is fine. That's a classical case of "if there's a doubt, there isn't a doubt": if the UI is *not* sure it won't call other methods, it *should* call .endParameters() It is *not* promised that there won't be multiple batches -- that's a QoI issue.
name change: [from .hasAnswer to] hasValue()?
Yep, definitely
No, we won't. We'll have copy.copy(klass.initParameters). No need to make it a callable, really.
name change for all of the following: *Parameter
Yes.
I misunderstood radix. Let me clarify: It's something like if you want to have a set of Servers in a selector: ArrayParameter(InterfaceParameter(ServerInterface))
class DictQuestion: (a dictionary mapping strings -> same kind of question)
I don't really get this one.
Think of a web Resource which is a folder. Then you can have {'children': DictParameter(InterfaceParameter(WebResourceInterface)) } [re __implements__]
Python will not have *any* implementation of interface checking stuff. Of course, we'll just take objects up on their __implements__ word, not check through methods.
I myself have misgivings on that. But maybe we can punt on it for now? -- "I'll be ex-DPL soon anyway so I'm |LUKE: Is Perl better than Python? looking for someplace else to grab power."|YODA: No...no... no. Quicker, -- Wichert Akkerman (on debian-private)| easier, more seductive. For public key, finger moshez@debian.org |http://www.{python,debian,gnu}.org
![](https://secure.gravatar.com/avatar/faeb19fbb879e56c1be4a300cfd80ec8.jpg?s=120&d=mm&r=g)
Thinking about how some UI for configuration would look like, namely command line syntax, I have some rough sketch. I wrote this "as if" the configuration interface I sketched earlier would actually exist. Note that the next UI to come would force a refactoring to "general config ui utility functions" and ui-specific functionality. I hope this will concretize the discussion First, some general words on how the ui will look like to the user: the commands will be
Wherever a parameter called "root" is mentioned, it will probably be the pickle file. Note also that this is far for complete. And last but not least, I didn't do the boring part of parsing the options and dispatching to the functions -- fill this in from imagination. Oh, and I obviously didn't test this wishful thinking code. #command line configuration def stringifyParameter(param): if isinstance(param, ArrayParameter): return "array of "+stringifyParameter(param.param)+'s' elif isinstance(param, DictParameter): return "folder of "+stringifyParameter(param.param)+'s' elif isinstance(param, StringParameter): return "string" elif isinstance(param, IntParameter): return "int" elif isinstance(param, InterfaceParameter): return param.interface.__name__ def convertString(type, s): if isinstance(type, ArrayParameter) or isinstance(type, DictParameter): raise ValueError("cannot set %s" % type.__class__.__name__) if isinstance(type, InterfaceParameter): obj = load(open(s)) if type.interface not in obj.__implements__: raise ValueError(obj.__class__.__name__+" does not implement "+ type.__class__.__name__) return obj if isinstance(type, StringParameter): return s if isinstance(type, IntParameter): return int(s) raise ValueError("unrecognized type" % type.__class__.__name__) def traverseObject(root, path): path = string.split(path, '.') name = path[-1] obj = root for part in path[:-1]: if isinstance(obj, ArrayParameter): obj = obj.getValue()[int(part)] elif isinstance(obj, DictParameter): obj = obj.getValue()[part] elif isinstance(obj, InterfaceParameter): obj = obj.getValue().getParameters()[part] param = obj.getParameters()[name] return param def setOption(root, path, value): param = traverseObject(root, path) value = convertString(param, value) obj.setAnswer(name, value) if hasattr(obj, 'endAnswer'): obj.endAnswer() def getOption(root, path): param = traverseObject(root, path) print stringifyParameter(param), param.getValue() def appendToArray(root, path, value): param = traverseObject(root, path) assert isinstance(param, ArrayParameter) value = convertString(param.param, value) param.getValue().append(value) def addToDict(root, path, name, value): param = traverseObject(root, path) assert isinstance(param, DictParameter) value = convertString(param.param, value) param.getValue()[name] = value -- "I'll be ex-DPL soon anyway so I'm |LUKE: Is Perl better than Python? looking for someplace else to grab power."|YODA: No...no... no. Quicker, -- Wichert Akkerman (on debian-private)| easier, more seductive. For public key, finger moshez@debian.org |http://www.{python,debian,gnu}.org
![](https://secure.gravatar.com/avatar/daf660d6890426570e2e2188f4f7b27e.jpg?s=120&d=mm&r=g)
On Fri, May 11, 2001 at 07:49:17PM +0300, Moshe Zadka wrote:
yes, that was a typo, I do know that there will be one call per parameter
Alright, I think I just misunderstood what you said about not executing methods until endParameters() is called. I thought you meant the object should be non-functional until endParamaters() is called. i realize now how stupid of a thought that was. :)
Why? I don't understand. (I should make this [TM])
Uhh.. isn't that what ListParameter is for?
Ok, I was thinking of PEP 0246. (http://python.sourceforge.net/peps/pep-0246.html)
The only advantage I see of mutliple question sections (outside of one set for instantiation and one for configuration) is a wizard-like interface, and I'm not so sure that's good anyway. :) -- Chris Armstrong carmstro@twistedmatrix.com http://twistedmatrix.com/~carmstro carmstro@dynup.net
![](https://secure.gravatar.com/avatar/faeb19fbb879e56c1be4a300cfd80ec8.jpg?s=120&d=mm&r=g)
On Sat, 12 May 2001 02:10:53 -0400, Chris Armstrong <carmstro@dynup.net> wrote:
Well, the object can afford to be non-functional until .endParameters() is called -- after all, no methods will expose it.
Because it will have a fixed template, which we then copy and change.
Uhh.. isn't that what ListParameter is for?
There's no ListParameter, and it would be wrong to call it that.
Which is only about *specifying* interfaces -- not checking them
And it's hard too -- we won't implement it. I'll take all this dialog and rewrite my original spec. -- "I'll be ex-DPL soon anyway so I'm |LUKE: Is Perl better than Python? looking for someplace else to grab power."|YODA: No...no... no. Quicker, -- Wichert Akkerman (on debian-private)| easier, more seductive. For public key, finger moshez@debian.org |http://www.{python,debian,gnu}.org
![](https://secure.gravatar.com/avatar/daf660d6890426570e2e2188f4f7b27e.jpg?s=120&d=mm&r=g)
[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:
Yes, this is definitely good, and planned this way.
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.
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.
name change: getParamaters().
name change: setParamaters() Also, throwing the exception is what's discouraged, not the actual use of the method. :)
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?)
name change: hasValue()?
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.
name change for all of the following: *Parameter
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.
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?)
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.
I'm not sure of my stance on this. I'll try to think of some examples and discuss. -- Chris Armstrong carmstro@twistedmatrix.com http://twistedmatrix.com/~carmstro carmstro@dynup.net
![](https://secure.gravatar.com/avatar/faeb19fbb879e56c1be4a300cfd80ec8.jpg?s=120&d=mm&r=g)
On Fri, 11 May 2001 19:36:24 +0300, Chris Armstrong <carmstro@dynup.net> wrote: <snipped agreement on the axioms>
setParameter() sans the 's'. One call per parameter
That's the classical example for why it's needed. Since the UI should do something like that for parameter in stuff_i_got: object.setParameter(...) object.endParameters() Then everything is fine. That's a classical case of "if there's a doubt, there isn't a doubt": if the UI is *not* sure it won't call other methods, it *should* call .endParameters() It is *not* promised that there won't be multiple batches -- that's a QoI issue.
name change: [from .hasAnswer to] hasValue()?
Yep, definitely
No, we won't. We'll have copy.copy(klass.initParameters). No need to make it a callable, really.
name change for all of the following: *Parameter
Yes.
I misunderstood radix. Let me clarify: It's something like if you want to have a set of Servers in a selector: ArrayParameter(InterfaceParameter(ServerInterface))
class DictQuestion: (a dictionary mapping strings -> same kind of question)
I don't really get this one.
Think of a web Resource which is a folder. Then you can have {'children': DictParameter(InterfaceParameter(WebResourceInterface)) } [re __implements__]
Python will not have *any* implementation of interface checking stuff. Of course, we'll just take objects up on their __implements__ word, not check through methods.
I myself have misgivings on that. But maybe we can punt on it for now? -- "I'll be ex-DPL soon anyway so I'm |LUKE: Is Perl better than Python? looking for someplace else to grab power."|YODA: No...no... no. Quicker, -- Wichert Akkerman (on debian-private)| easier, more seductive. For public key, finger moshez@debian.org |http://www.{python,debian,gnu}.org
![](https://secure.gravatar.com/avatar/faeb19fbb879e56c1be4a300cfd80ec8.jpg?s=120&d=mm&r=g)
Thinking about how some UI for configuration would look like, namely command line syntax, I have some rough sketch. I wrote this "as if" the configuration interface I sketched earlier would actually exist. Note that the next UI to come would force a refactoring to "general config ui utility functions" and ui-specific functionality. I hope this will concretize the discussion First, some general words on how the ui will look like to the user: the commands will be
Wherever a parameter called "root" is mentioned, it will probably be the pickle file. Note also that this is far for complete. And last but not least, I didn't do the boring part of parsing the options and dispatching to the functions -- fill this in from imagination. Oh, and I obviously didn't test this wishful thinking code. #command line configuration def stringifyParameter(param): if isinstance(param, ArrayParameter): return "array of "+stringifyParameter(param.param)+'s' elif isinstance(param, DictParameter): return "folder of "+stringifyParameter(param.param)+'s' elif isinstance(param, StringParameter): return "string" elif isinstance(param, IntParameter): return "int" elif isinstance(param, InterfaceParameter): return param.interface.__name__ def convertString(type, s): if isinstance(type, ArrayParameter) or isinstance(type, DictParameter): raise ValueError("cannot set %s" % type.__class__.__name__) if isinstance(type, InterfaceParameter): obj = load(open(s)) if type.interface not in obj.__implements__: raise ValueError(obj.__class__.__name__+" does not implement "+ type.__class__.__name__) return obj if isinstance(type, StringParameter): return s if isinstance(type, IntParameter): return int(s) raise ValueError("unrecognized type" % type.__class__.__name__) def traverseObject(root, path): path = string.split(path, '.') name = path[-1] obj = root for part in path[:-1]: if isinstance(obj, ArrayParameter): obj = obj.getValue()[int(part)] elif isinstance(obj, DictParameter): obj = obj.getValue()[part] elif isinstance(obj, InterfaceParameter): obj = obj.getValue().getParameters()[part] param = obj.getParameters()[name] return param def setOption(root, path, value): param = traverseObject(root, path) value = convertString(param, value) obj.setAnswer(name, value) if hasattr(obj, 'endAnswer'): obj.endAnswer() def getOption(root, path): param = traverseObject(root, path) print stringifyParameter(param), param.getValue() def appendToArray(root, path, value): param = traverseObject(root, path) assert isinstance(param, ArrayParameter) value = convertString(param.param, value) param.getValue().append(value) def addToDict(root, path, name, value): param = traverseObject(root, path) assert isinstance(param, DictParameter) value = convertString(param.param, value) param.getValue()[name] = value -- "I'll be ex-DPL soon anyway so I'm |LUKE: Is Perl better than Python? looking for someplace else to grab power."|YODA: No...no... no. Quicker, -- Wichert Akkerman (on debian-private)| easier, more seductive. For public key, finger moshez@debian.org |http://www.{python,debian,gnu}.org
![](https://secure.gravatar.com/avatar/daf660d6890426570e2e2188f4f7b27e.jpg?s=120&d=mm&r=g)
On Fri, May 11, 2001 at 07:49:17PM +0300, Moshe Zadka wrote:
yes, that was a typo, I do know that there will be one call per parameter
Alright, I think I just misunderstood what you said about not executing methods until endParameters() is called. I thought you meant the object should be non-functional until endParamaters() is called. i realize now how stupid of a thought that was. :)
Why? I don't understand. (I should make this [TM])
Uhh.. isn't that what ListParameter is for?
Ok, I was thinking of PEP 0246. (http://python.sourceforge.net/peps/pep-0246.html)
The only advantage I see of mutliple question sections (outside of one set for instantiation and one for configuration) is a wizard-like interface, and I'm not so sure that's good anyway. :) -- Chris Armstrong carmstro@twistedmatrix.com http://twistedmatrix.com/~carmstro carmstro@dynup.net
![](https://secure.gravatar.com/avatar/faeb19fbb879e56c1be4a300cfd80ec8.jpg?s=120&d=mm&r=g)
On Sat, 12 May 2001 02:10:53 -0400, Chris Armstrong <carmstro@dynup.net> wrote:
Well, the object can afford to be non-functional until .endParameters() is called -- after all, no methods will expose it.
Because it will have a fixed template, which we then copy and change.
Uhh.. isn't that what ListParameter is for?
There's no ListParameter, and it would be wrong to call it that.
Which is only about *specifying* interfaces -- not checking them
And it's hard too -- we won't implement it. I'll take all this dialog and rewrite my original spec. -- "I'll be ex-DPL soon anyway so I'm |LUKE: Is Perl better than Python? looking for someplace else to grab power."|YODA: No...no... no. Quicker, -- Wichert Akkerman (on debian-private)| easier, more seductive. For public key, finger moshez@debian.org |http://www.{python,debian,gnu}.org
participants (2)
-
Chris Armstrong
-
Moshe Zadka