[Twisted-Python] Configuration, Final Spec

Glyph Lefkowitz glyph at twistedmatrix.com
Sat May 12 19:03:15 EDT 2001


On Saturday 12 May 2001 02:32, you wrote:
> Rewritten, standalone, spec

[ agreement with axioms ]

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

IMHO, people *should* inherit from it, so that we can have some visibility on 
what implements it.  I agree that it should provide as little functionality 
as possible, however.

> .getParameters() --> return a dictionary: name of parameter, Parameter
> object .setParameter(name, answer) --> notify the application that an
> answer has been given to a particular question
>                              It can throw an InvalidParameter exception
> with a string for a reason. This is for
>                              application-level verification. Note that
>                              application-level verification is discouraged.
> .endParameters() --> the "application" promises that no methods of the
> object will be called between a series of .setParameter()s and
> .endParameters(). So, this means that if the UI got a bunch of answers, it
> will call .setParameter() several times, and then .endParameters(). The UI
> will *check* for this method's existance, and will not call it if it
> doesn't exist. .setParent(parent) --> if the object is created inside an
> InterfaceProperty of some other object, module nested ArrayParameters and
> DictParameters, .setParent will be called with that object, *if it exists*.

I don't understand this part of the design.

In my humble (but correct) opinion, it is *always* bad API design to force 
the caller to "promise" something.  (Sometimes, other design concerns 
mitigate this decision, but I don't see one here.  However, I have frequently 
joked about having a neon sign in my office, for design discussions, that 
says "IT IS NOT THE CALLER'S RESPONSIBILITY" in 10-foot-tall letters)

I would much rather see a .getParameters() -> dict / .setParameters(dict).  
It seems more symmetrical, and if you can "promise" that (A) setParameters 
may be called some number of times and (B) endParameters() will *always* be 
called afterwards, we can put that loop into the config side of the code.

Any reasons why it can't be done this way?

> Parameter 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:

Of the "Parameter", I take it you mean? :)

> .hasValue() --> boolean, whether the Parameter already contains an
>                 value
> .getValue() --> will only work if .hasValue() is true, returns the answer
> .setValue(val) --> make .hasValue() true
> .title --> A string, the human-readable description of the property.
>
> Objects which can be created by the UI should have an initParameters
> attribute which should be a sequence of Parameters, which correspond
> to the mandatory arguments of the __init__ method.

Another interesting open question (although not one I'm sure this code has to 
address) is how to get the respective configuration interfaces to represent 
object identity.  (e.g. I want to mount the *same* web resource on /foo, on 
/bar)

> This leaves open the question of how classes register with the UI at all.
> A possibly radical solution will be to have the UI scan through
> sys.modules, and each class will be checked for the __implements__
> attribute. A less radical option is to have a register(klass) function
> which registers it with the UI. register() can be used from within the
> module, if it is an active participant, or from within an importing module.

There is a third option -- make Configurable a metaclass :)

However, I prefer the register(klass) way, both for clarity and efficiency.  
Explicit is better than implicit.

> example:
>
> class Server: # note -- not inheriting from anything
>
>     initParameters = (StringParameter("Server Name"), IntParameter("Port"))
>     def __init__(self, name, port):
>         self.name, self.port = name, port
>
>     def getParameters(self):
>         name = StringQuestion("Server Name")
>         name.setValue(self.name)
>         port = IntQuestion("Port")
>         port.setValue(self.port)
>         return {'port': port, 'name': name}
>
>     def setParameter(self, name, answer):
>         # note: no need to use int(answer)
>         # for port: an IntQuestion has an integer as a .getValue()
>         setattr(self, name, answer)

Your code has a bug in it, which is a good example of why I like to actually 
inherit from interfaces :).  You don't define "endParameters".

One more question -- why doesn't the Configurable interface class actually do 
some things, like data validation? It sounds like it'd be possible 
(isCorrectInput method on the Parameter classes, or somesuch...)

On the whole this looks like a good proposal, although I'm still not entirely 
sure what the final end-user experience of configuration will look like.

-- 
                      ______      __   __  _____  _     _
                     |  ____ |      \_/   |_____] |_____|
                     |_____| |_____  |    |       |     |
                     @ t w i s t e d m a t r i x  . c o m
                     http://twistedmatrix.com/users/glyph





More information about the Twisted-Python mailing list